Merge remote-tracking branch 'origin/feat_receipt_upload'
This commit is contained in:
parent
d311b6f8cf
commit
5e4a59e15f
21
Dockerfile
21
Dockerfile
@ -1,23 +1,26 @@
|
|||||||
FROM python@sha256:075fe10ae13ea0f081540bead850eeb7b6c71d07ed4766d75f8529abd0101c44
|
FROM python@sha256:21c9f0b22213295a13bd678c5b45aa587ff6cb01cd99b6cf0e6928f4c777006b
|
||||||
# python:3.10.10-slim-bullseye
|
# python:3.11.4-slim-bullseye (arm/v7)
|
||||||
RUN useradd costhive
|
RUN useradd costhive
|
||||||
|
|
||||||
WORKDIR /home/costhive
|
WORKDIR /home/costhive
|
||||||
|
|
||||||
RUN apt update && apt -y upgrade
|
RUN apt update && apt -y upgrade; \
|
||||||
RUN apt install -y libpq-dev gcc g++ swig make
|
apt install -y libpq-dev gcc g++ swig make; \
|
||||||
|
rm -rf /var/lib/apt/lists
|
||||||
|
|
||||||
COPY boot.sh requirements.txt ./
|
COPY boot.sh requirements.txt ./
|
||||||
RUN python -m venv venv
|
RUN python -m venv venv; \
|
||||||
RUN venv/bin/pip install -r requirements.txt
|
venv/bin/pip install --upgrade pip; \
|
||||||
RUN venv/bin/pip install gunicorn
|
venv/bin/pip install wheel; \
|
||||||
RUN chmod +x boot.sh
|
venv/bin/pip install gunicorn; \
|
||||||
|
venv/bin/pip install -r requirements.txt
|
||||||
|
|
||||||
COPY backend backend
|
COPY backend backend
|
||||||
|
|
||||||
ENV FLASK_APP run.py
|
ENV FLASK_APP run.py
|
||||||
|
|
||||||
RUN chown -R costhive:costhive ./
|
RUN chmod +x boot.sh; \
|
||||||
|
chown -R costhive:costhive ./
|
||||||
|
|
||||||
USER costhive
|
USER costhive
|
||||||
|
|
||||||
|
|||||||
12
Pipfile
12
Pipfile
@ -1,12 +0,0 @@
|
|||||||
[[source]]
|
|
||||||
url = "https://pypi.org/simple"
|
|
||||||
verify_ssl = true
|
|
||||||
name = "pypi"
|
|
||||||
|
|
||||||
[packages]
|
|
||||||
flask-cors = "*"
|
|
||||||
|
|
||||||
[dev-packages]
|
|
||||||
|
|
||||||
[requires]
|
|
||||||
python_version = "3.10"
|
|
||||||
141
Pipfile.lock
generated
141
Pipfile.lock
generated
@ -1,141 +0,0 @@
|
|||||||
{
|
|
||||||
"_meta": {
|
|
||||||
"hash": {
|
|
||||||
"sha256": "2f46680952b8ccfaf093f8ef1315053afb3d2e15fc244d50f313d0961890dfc3"
|
|
||||||
},
|
|
||||||
"pipfile-spec": 6,
|
|
||||||
"requires": {
|
|
||||||
"python_version": "3.10"
|
|
||||||
},
|
|
||||||
"sources": [
|
|
||||||
{
|
|
||||||
"name": "pypi",
|
|
||||||
"url": "https://pypi.org/simple",
|
|
||||||
"verify_ssl": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"default": {
|
|
||||||
"click": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
|
|
||||||
"sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"
|
|
||||||
],
|
|
||||||
"markers": "python_version >= '3.7'",
|
|
||||||
"version": "==8.1.3"
|
|
||||||
},
|
|
||||||
"colorama": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44",
|
|
||||||
"sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"
|
|
||||||
],
|
|
||||||
"markers": "platform_system == 'Windows'",
|
|
||||||
"version": "==0.4.6"
|
|
||||||
},
|
|
||||||
"flask": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:7eb373984bf1c770023fce9db164ed0c3353cd0b53f130f4693da0ca756a2e6d",
|
|
||||||
"sha256:c0bec9477df1cb867e5a67c9e1ab758de9cb4a3e52dd70681f59fa40a62b3f2d"
|
|
||||||
],
|
|
||||||
"markers": "python_version >= '3.7'",
|
|
||||||
"version": "==2.2.3"
|
|
||||||
},
|
|
||||||
"flask-cors": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438",
|
|
||||||
"sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==3.0.10"
|
|
||||||
},
|
|
||||||
"itsdangerous": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44",
|
|
||||||
"sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"
|
|
||||||
],
|
|
||||||
"markers": "python_version >= '3.7'",
|
|
||||||
"version": "==2.1.2"
|
|
||||||
},
|
|
||||||
"jinja2": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852",
|
|
||||||
"sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"
|
|
||||||
],
|
|
||||||
"markers": "python_version >= '3.7'",
|
|
||||||
"version": "==3.1.2"
|
|
||||||
},
|
|
||||||
"markupsafe": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed",
|
|
||||||
"sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc",
|
|
||||||
"sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2",
|
|
||||||
"sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460",
|
|
||||||
"sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7",
|
|
||||||
"sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0",
|
|
||||||
"sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1",
|
|
||||||
"sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa",
|
|
||||||
"sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03",
|
|
||||||
"sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323",
|
|
||||||
"sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65",
|
|
||||||
"sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013",
|
|
||||||
"sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036",
|
|
||||||
"sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f",
|
|
||||||
"sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4",
|
|
||||||
"sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419",
|
|
||||||
"sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2",
|
|
||||||
"sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619",
|
|
||||||
"sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a",
|
|
||||||
"sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a",
|
|
||||||
"sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd",
|
|
||||||
"sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7",
|
|
||||||
"sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666",
|
|
||||||
"sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65",
|
|
||||||
"sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859",
|
|
||||||
"sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625",
|
|
||||||
"sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff",
|
|
||||||
"sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156",
|
|
||||||
"sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd",
|
|
||||||
"sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba",
|
|
||||||
"sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f",
|
|
||||||
"sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1",
|
|
||||||
"sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094",
|
|
||||||
"sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a",
|
|
||||||
"sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513",
|
|
||||||
"sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed",
|
|
||||||
"sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d",
|
|
||||||
"sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3",
|
|
||||||
"sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147",
|
|
||||||
"sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c",
|
|
||||||
"sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603",
|
|
||||||
"sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601",
|
|
||||||
"sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a",
|
|
||||||
"sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1",
|
|
||||||
"sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d",
|
|
||||||
"sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3",
|
|
||||||
"sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54",
|
|
||||||
"sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2",
|
|
||||||
"sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6",
|
|
||||||
"sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"
|
|
||||||
],
|
|
||||||
"markers": "python_version >= '3.7'",
|
|
||||||
"version": "==2.1.2"
|
|
||||||
},
|
|
||||||
"six": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
|
|
||||||
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
|
|
||||||
],
|
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
|
|
||||||
"version": "==1.16.0"
|
|
||||||
},
|
|
||||||
"werkzeug": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:2e1ccc9417d4da358b9de6f174e3ac094391ea1d4fbef2d667865d819dfd0afe",
|
|
||||||
"sha256:56433961bc1f12533306c624f3be5e744389ac61d722175d543e1751285da612"
|
|
||||||
],
|
|
||||||
"markers": "python_version >= '3.7'",
|
|
||||||
"version": "==2.2.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"develop": {}
|
|
||||||
}
|
|
||||||
@ -7,22 +7,23 @@ name = "pypi"
|
|||||||
email-validator = "*"
|
email-validator = "*"
|
||||||
flask = "*"
|
flask = "*"
|
||||||
flask-bootstrap = "*"
|
flask-bootstrap = "*"
|
||||||
|
flask-cors = "*"
|
||||||
flask-login = "*"
|
flask-login = "*"
|
||||||
flask-mail = "*"
|
flask-mail = "*"
|
||||||
|
flask-marshmallow = "*"
|
||||||
flask-migrate = "*"
|
flask-migrate = "*"
|
||||||
flask-sqlalchemy = "*"
|
flask-sqlalchemy = "*"
|
||||||
flask-wtf = "*"
|
flask-wtf = "*"
|
||||||
|
marshmallow-sqlalchemy = "*"
|
||||||
psycopg2-binary = "*"
|
psycopg2-binary = "*"
|
||||||
pyjwt = "*"
|
pyjwt = "*"
|
||||||
python-dotenv = "*"
|
python-dotenv = "*"
|
||||||
pymupdf = "*"
|
pymupdf = "*"
|
||||||
sqlalchemy-utils = "*"
|
|
||||||
flask-marshmallow = "*"
|
|
||||||
marshmallow-sqlalchemy = "*"
|
|
||||||
requests = "*"
|
requests = "*"
|
||||||
|
sqlalchemy-utils = "*"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
|
|
||||||
[requires]
|
[requires]
|
||||||
python_version = "3.10"
|
python_version = "3.11"
|
||||||
python_full_version = "3.10.10"
|
python_full_version = "3.11.4"
|
||||||
|
|||||||
312
backend/Pipfile.lock
generated
312
backend/Pipfile.lock
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "d2a442d0df6bf1581b3b231990af6de00853e9a6c1381b756a8056ac9f2bead3"
|
"sha256": "6aa4fc89c41e2a25283179a13d468214fdebcbd824a74a7f2f79482b920eed78"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
"python_full_version": "3.10.10",
|
"python_full_version": "3.11.4",
|
||||||
"python_version": "3.10"
|
"python_version": "3.11"
|
||||||
},
|
},
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
@ -25,6 +25,14 @@
|
|||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==1.11.1"
|
"version": "==1.11.1"
|
||||||
},
|
},
|
||||||
|
"anyio": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780",
|
||||||
|
"sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.7'",
|
||||||
|
"version": "==3.7.1"
|
||||||
|
},
|
||||||
"blinker": {
|
"blinker": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:4afd3de66ef3a9f8067559fb7a1cbe555c17dcbe15971b05d1b625c3e7abe213",
|
"sha256:4afd3de66ef3a9f8067559fb7a1cbe555c17dcbe15971b05d1b625c3e7abe213",
|
||||||
@ -43,92 +51,92 @@
|
|||||||
},
|
},
|
||||||
"charset-normalizer": {
|
"charset-normalizer": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6",
|
"sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96",
|
||||||
"sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1",
|
"sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c",
|
||||||
"sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e",
|
"sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710",
|
||||||
"sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373",
|
"sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706",
|
||||||
"sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62",
|
"sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020",
|
||||||
"sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230",
|
"sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252",
|
||||||
"sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be",
|
"sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad",
|
||||||
"sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c",
|
"sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329",
|
||||||
"sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0",
|
"sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a",
|
||||||
"sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448",
|
"sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f",
|
||||||
"sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f",
|
"sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6",
|
||||||
"sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649",
|
"sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4",
|
||||||
"sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d",
|
"sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a",
|
||||||
"sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0",
|
"sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46",
|
||||||
"sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706",
|
"sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2",
|
||||||
"sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a",
|
"sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23",
|
||||||
"sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59",
|
"sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace",
|
||||||
"sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23",
|
"sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd",
|
||||||
"sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5",
|
"sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982",
|
||||||
"sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb",
|
"sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10",
|
||||||
"sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e",
|
"sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2",
|
||||||
"sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e",
|
"sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea",
|
||||||
"sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c",
|
"sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09",
|
||||||
"sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28",
|
"sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5",
|
||||||
"sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d",
|
"sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149",
|
||||||
"sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41",
|
"sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489",
|
||||||
"sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974",
|
"sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9",
|
||||||
"sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce",
|
"sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80",
|
||||||
"sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f",
|
"sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592",
|
||||||
"sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1",
|
"sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3",
|
||||||
"sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d",
|
"sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6",
|
||||||
"sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8",
|
"sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed",
|
||||||
"sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017",
|
"sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c",
|
||||||
"sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31",
|
"sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200",
|
||||||
"sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7",
|
"sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a",
|
||||||
"sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8",
|
"sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e",
|
||||||
"sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e",
|
"sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d",
|
||||||
"sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14",
|
"sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6",
|
||||||
"sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd",
|
"sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623",
|
||||||
"sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d",
|
"sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669",
|
||||||
"sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795",
|
"sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3",
|
||||||
"sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b",
|
"sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa",
|
||||||
"sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b",
|
"sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9",
|
||||||
"sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b",
|
"sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2",
|
||||||
"sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203",
|
"sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f",
|
||||||
"sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f",
|
"sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1",
|
||||||
"sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19",
|
"sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4",
|
||||||
"sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1",
|
"sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a",
|
||||||
"sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a",
|
"sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8",
|
||||||
"sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac",
|
"sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3",
|
||||||
"sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9",
|
"sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029",
|
||||||
"sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0",
|
"sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f",
|
||||||
"sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137",
|
"sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959",
|
||||||
"sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f",
|
"sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22",
|
||||||
"sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6",
|
"sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7",
|
||||||
"sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5",
|
"sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952",
|
||||||
"sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909",
|
"sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346",
|
||||||
"sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f",
|
"sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e",
|
||||||
"sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0",
|
"sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d",
|
||||||
"sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324",
|
"sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299",
|
||||||
"sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755",
|
"sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd",
|
||||||
"sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb",
|
"sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a",
|
||||||
"sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854",
|
"sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3",
|
||||||
"sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c",
|
"sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037",
|
||||||
"sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60",
|
"sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94",
|
||||||
"sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84",
|
"sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c",
|
||||||
"sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0",
|
"sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858",
|
||||||
"sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b",
|
"sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a",
|
||||||
"sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1",
|
"sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449",
|
||||||
"sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531",
|
"sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c",
|
||||||
"sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1",
|
"sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918",
|
||||||
"sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11",
|
"sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1",
|
||||||
"sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326",
|
"sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c",
|
||||||
"sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df",
|
"sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac",
|
||||||
"sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"
|
"sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"
|
||||||
],
|
],
|
||||||
"markers": "python_full_version >= '3.7.0'",
|
"markers": "python_full_version >= '3.7.0'",
|
||||||
"version": "==3.1.0"
|
"version": "==3.2.0"
|
||||||
},
|
},
|
||||||
"click": {
|
"click": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
|
"sha256:4be4b1af8d665c6d942909916d31a213a106800c47d0eeba73d34da3cbc11367",
|
||||||
"sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"
|
"sha256:e576aa487d679441d7d30abb87e1b43d24fc53bffb8758443b1a9e1cee504548"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==8.1.3"
|
"version": "==8.1.5"
|
||||||
},
|
},
|
||||||
"colorama": {
|
"colorama": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -140,11 +148,11 @@
|
|||||||
},
|
},
|
||||||
"dnspython": {
|
"dnspython": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:224e32b03eb46be70e12ef6d64e0be123a64e621ab4c0822ff6d450d52a540b9",
|
"sha256:46b4052a55b56beea3a3bdd7b30295c292bd6827dd442348bc116f2d35b17f0a",
|
||||||
"sha256:89141536394f909066cabd112e3e1a37e4e654db00a25308b0f130bc3152eb46"
|
"sha256:758e691dbb454d5ccf4e1b154a19e52847f79e21a42fef17b969144af29a4e6c"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.7' and python_version < '4.0'",
|
"markers": "python_version >= '3.8' and python_version < '4.0'",
|
||||||
"version": "==2.3.0"
|
"version": "==2.4.0"
|
||||||
},
|
},
|
||||||
"dominate": {
|
"dominate": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -162,6 +170,14 @@
|
|||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==2.0.0.post2"
|
"version": "==2.0.0.post2"
|
||||||
},
|
},
|
||||||
|
"exceptiongroup": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5",
|
||||||
|
"sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"
|
||||||
|
],
|
||||||
|
"markers": "python_version < '3.11'",
|
||||||
|
"version": "==1.1.2"
|
||||||
|
},
|
||||||
"flask": {
|
"flask": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:77fd4e1249d8c9923de34907236b747ced06e5467ecac1a7bb7115ae0e9670b0",
|
"sha256:77fd4e1249d8c9923de34907236b747ced06e5467ecac1a7bb7115ae0e9670b0",
|
||||||
@ -177,6 +193,14 @@
|
|||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==3.3.7.1"
|
"version": "==3.3.7.1"
|
||||||
},
|
},
|
||||||
|
"flask-cors": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:bc3492bfd6368d27cfe79c7821df5a8a319e1a6d5eab277a3794be19bdc51783",
|
||||||
|
"sha256:f268522fcb2f73e2ecdde1ef45e2fd5c71cc48fe03cffb4b441c6d1b40684eb0"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==4.0.0"
|
||||||
|
},
|
||||||
"flask-login": {
|
"flask-login": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:1ef79843f5eddd0f143c2cd994c1b05ac83c0401dc6234c143495af9a939613f",
|
"sha256:1ef79843f5eddd0f143c2cd994c1b05ac83c0401dc6234c143495af9a939613f",
|
||||||
@ -290,6 +314,22 @@
|
|||||||
"markers": "platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))",
|
"markers": "platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))",
|
||||||
"version": "==2.0.2"
|
"version": "==2.0.2"
|
||||||
},
|
},
|
||||||
|
"h11": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d",
|
||||||
|
"sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.7'",
|
||||||
|
"version": "==0.14.0"
|
||||||
|
},
|
||||||
|
"httpcore": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:a6f30213335e34c1ade7be6ec7c47f19f50c56db36abef1a9dfa3815b1cb3888",
|
||||||
|
"sha256:c2789b767ddddfa2a5782e3199b2b7f6894540b17b16ec26b2c4d8e103510b87"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.8'",
|
||||||
|
"version": "==0.17.3"
|
||||||
|
},
|
||||||
"idna": {
|
"idna": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4",
|
"sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4",
|
||||||
@ -530,52 +570,60 @@
|
|||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==2.31.0"
|
"version": "==2.31.0"
|
||||||
},
|
},
|
||||||
"sqlalchemy": {
|
"sniffio": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:04383f1e3452f6739084184e427e9d5cb4e68ddc765d52157bf5ef30d5eca14f",
|
"sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101",
|
||||||
"sha256:125f9f7e62ddf8b590c069729080ffe18b68a20d9882eb0947f72e06274601d7",
|
"sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"
|
||||||
"sha256:1822620c89779b85f7c23d535c8e04b79c517739ae07aaed48c81e591ed5498e",
|
|
||||||
"sha256:21583808d37f126a647652c90332ac1d3a102edf3c94bcc3319edcc0ea2300cc",
|
|
||||||
"sha256:218fb20c01e95004f50a3062bf4c447dcb360cab8274232f31947e254f118298",
|
|
||||||
"sha256:2269b1f9b8be47e52b70936069a25a3771eff53367aa5cc59bb94f28a6412e13",
|
|
||||||
"sha256:234678ed6576531b8e4be255b980f20368bf07241a2e67b84e6b0fe679edb9c4",
|
|
||||||
"sha256:28da17059ecde53e2d10ba813d38db942b9f6344360b2958b25872d5cb729d35",
|
|
||||||
"sha256:2c6ff5767d954f6091113fedcaaf49cdec2197ae4c5301fe83d5ae4393c82f33",
|
|
||||||
"sha256:36a87e26fe8fa8c466fae461a8fcb780d0a1cbf8206900759fc6fe874475a3ce",
|
|
||||||
"sha256:394ac3adf3676fad76d4b8fcecddf747627f17f0738dc94bac15f303d05b03d4",
|
|
||||||
"sha256:40a3dc52b2b16f08b5c16b9ee7646329e4b3411e9280e5e8d57b19eaa51cbef4",
|
|
||||||
"sha256:48111d56afea5699bab72c38ec95561796b81befff9e13d1dd5ce251ab25f51d",
|
|
||||||
"sha256:48b40dc2895841ea89d89df9eb3ac69e2950a659db20a369acf4259f68e6dc1f",
|
|
||||||
"sha256:513411d73503a6fc5804f01fae3b3d44f267c1b3a06cfeac02e9286a7330e857",
|
|
||||||
"sha256:51736cfb607cf4e8fafb693906f9bc4e5ee55be0b096d44bd7f20cd8489b8571",
|
|
||||||
"sha256:5f40e3a7d0a464f1c8593f2991e5520b2f5b26da24e88000bbd4423f86103d4f",
|
|
||||||
"sha256:6150560fcffc6aee5ec9a97419ac768c7a9f56baf7a7eb59cb4b1b6a4d463ad9",
|
|
||||||
"sha256:724355973297bbe547f3eb98b46ade65a67a3d5a6303f17ab59a2dc6fb938943",
|
|
||||||
"sha256:74ddcafb6488f382854a7da851c404c394be3729bb3d91b02ad86c5458140eff",
|
|
||||||
"sha256:7830e01b02d440c27f2a5be68296e74ccb55e6a5b5962ffafd360b98930b2e5e",
|
|
||||||
"sha256:7f31d4e7ca1dd8ca5a27fd5eaa0f9e2732fe769ff7dd35bf7bba179597e4df07",
|
|
||||||
"sha256:8741d3d401383e54b2aada37cbd10f55c5d444b360eae3a82f74a2be568a7710",
|
|
||||||
"sha256:910d45bf3673f0e4ef13858674bd23cfdafdc8368b45b948bf511797dbbb401d",
|
|
||||||
"sha256:aa995b21f853864996e4056d9fde479bcecf8b7bff4beb3555eebbbba815f35d",
|
|
||||||
"sha256:af7e2ba75bf84b64adb331918188dda634689a2abb151bc1a583e488363fd2f8",
|
|
||||||
"sha256:b0eaf82cc844f6b46defe15ad243ea00d1e39ed3859df61130c263dc7204da6e",
|
|
||||||
"sha256:b114a16bc03dfe20b625062e456affd7b9938286e05a3f904a025b9aacc29dd4",
|
|
||||||
"sha256:b47be4c6281a86670ea5cfbbbe6c3a65366a8742f5bc8b986f790533c60b5ddb",
|
|
||||||
"sha256:ba03518e64d86f000dc24ab3d3a1aa876bcbaa8aa15662ac2df5e81537fa3394",
|
|
||||||
"sha256:cc9c2630c423ac4973492821b2969f5fe99d9736f3025da670095668fbfcd4d5",
|
|
||||||
"sha256:cf07ff9920cb3ca9d73525dfd4f36ddf9e1a83734ea8b4f724edfd9a2c6e82d9",
|
|
||||||
"sha256:cf175d26f6787cce30fe6c04303ca0aeeb0ad40eeb22e3391f24b32ec432a1e1",
|
|
||||||
"sha256:d0aeb3afaa19f187a70fa592fbe3c20a056b57662691fd3abf60f016aa5c1848",
|
|
||||||
"sha256:e186e9e95fb5d993b075c33fe4f38a22105f7ce11cecb5c17b5618181e356702",
|
|
||||||
"sha256:e2d5c3596254cf1a96474b98e7ce20041c74c008b0f101c1cb4f8261cb77c6d3",
|
|
||||||
"sha256:e3189432db2f5753b4fde1aa90a61c69976f4e7e31d1cf4611bfe3514ed07478",
|
|
||||||
"sha256:e3a6b2788f193756076061626679c5c5a6d600ddf8324f986bc72004c3e9d92e",
|
|
||||||
"sha256:ead58cae2a089eee1b0569060999cb5f2b2462109498a0937cc230a7556945a1",
|
|
||||||
"sha256:f2f389f77c68dc22cb51f026619291c4a38aeb4b7ecb5f998fd145b2d81ca513",
|
|
||||||
"sha256:f593170fc09c5abb1205a738290b39532f7380094dc151805009a07ae0e85330"
|
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==2.0.17"
|
"version": "==1.3.0"
|
||||||
|
},
|
||||||
|
"sqlalchemy": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:024d2f67fb3ec697555e48caeb7147cfe2c08065a4f1a52d93c3d44fc8e6ad1c",
|
||||||
|
"sha256:0bf0fd65b50a330261ec7fe3d091dfc1c577483c96a9fa1e4323e932961aa1b5",
|
||||||
|
"sha256:16a310f5bc75a5b2ce7cb656d0e76eb13440b8354f927ff15cbaddd2523ee2d1",
|
||||||
|
"sha256:1d90ccc15ba1baa345796a8fb1965223ca7ded2d235ccbef80a47b85cea2d71a",
|
||||||
|
"sha256:22bafb1da60c24514c141a7ff852b52f9f573fb933b1e6b5263f0daa28ce6db9",
|
||||||
|
"sha256:2c69ce70047b801d2aba3e5ff3cba32014558966109fecab0c39d16c18510f15",
|
||||||
|
"sha256:2e7b69d9ced4b53310a87117824b23c509c6fc1f692aa7272d47561347e133b6",
|
||||||
|
"sha256:314145c1389b021a9ad5aa3a18bac6f5d939f9087d7fc5443be28cba19d2c972",
|
||||||
|
"sha256:3afa8a21a9046917b3a12ffe016ba7ebe7a55a6fc0c7d950beb303c735c3c3ad",
|
||||||
|
"sha256:430614f18443b58ceb9dedec323ecddc0abb2b34e79d03503b5a7579cd73a531",
|
||||||
|
"sha256:43699eb3f80920cc39a380c159ae21c8a8924fe071bccb68fc509e099420b148",
|
||||||
|
"sha256:539010665c90e60c4a1650afe4ab49ca100c74e6aef882466f1de6471d414be7",
|
||||||
|
"sha256:57d100a421d9ab4874f51285c059003292433c648df6abe6c9c904e5bd5b0828",
|
||||||
|
"sha256:5831138f0cc06b43edf5f99541c64adf0ab0d41f9a4471fd63b54ae18399e4de",
|
||||||
|
"sha256:584f66e5e1979a7a00f4935015840be627e31ca29ad13f49a6e51e97a3fb8cae",
|
||||||
|
"sha256:5d6afc41ca0ecf373366fd8e10aee2797128d3ae45eb8467b19da4899bcd1ee0",
|
||||||
|
"sha256:61ada5831db36d897e28eb95f0f81814525e0d7927fb51145526c4e63174920b",
|
||||||
|
"sha256:6b54d1ad7a162857bb7c8ef689049c7cd9eae2f38864fc096d62ae10bc100c7d",
|
||||||
|
"sha256:7351c05db355da112e056a7b731253cbeffab9dfdb3be1e895368513c7d70106",
|
||||||
|
"sha256:77a14fa20264af73ddcdb1e2b9c5a829b8cc6b8304d0f093271980e36c200a3f",
|
||||||
|
"sha256:851a37898a8a39783aab603c7348eb5b20d83c76a14766a43f56e6ad422d1ec8",
|
||||||
|
"sha256:89bc2b374ebee1a02fd2eae6fd0570b5ad897ee514e0f84c5c137c942772aa0c",
|
||||||
|
"sha256:8e712cfd2e07b801bc6b60fdf64853bc2bd0af33ca8fa46166a23fe11ce0dbb0",
|
||||||
|
"sha256:8f9eb4575bfa5afc4b066528302bf12083da3175f71b64a43a7c0badda2be365",
|
||||||
|
"sha256:8fc05b59142445a4efb9c1fd75c334b431d35c304b0e33f4fa0ff1ea4890f92e",
|
||||||
|
"sha256:96f0463573469579d32ad0c91929548d78314ef95c210a8115346271beeeaaa2",
|
||||||
|
"sha256:9deaae357edc2091a9ed5d25e9ee8bba98bcfae454b3911adeaf159c2e9ca9e3",
|
||||||
|
"sha256:a752b7a9aceb0ba173955d4f780c64ee15a1a991f1c52d307d6215c6c73b3a4c",
|
||||||
|
"sha256:ae7473a67cd82a41decfea58c0eac581209a0aa30f8bc9190926fbf628bb17f7",
|
||||||
|
"sha256:b15afbf5aa76f2241184c1d3b61af1a72ba31ce4161013d7cb5c4c2fca04fd6e",
|
||||||
|
"sha256:c896d4e6ab2eba2afa1d56be3d0b936c56d4666e789bfc59d6ae76e9fcf46145",
|
||||||
|
"sha256:cb4e688f6784427e5f9479d1a13617f573de8f7d4aa713ba82813bcd16e259d1",
|
||||||
|
"sha256:cda283700c984e699e8ef0fcc5c61f00c9d14b6f65a4f2767c97242513fcdd84",
|
||||||
|
"sha256:cf7b5e3856cbf1876da4e9d9715546fa26b6e0ba1a682d5ed2fc3ca4c7c3ec5b",
|
||||||
|
"sha256:d6894708eeb81f6d8193e996257223b6bb4041cb05a17cd5cf373ed836ef87a2",
|
||||||
|
"sha256:d8f2afd1aafded7362b397581772c670f20ea84d0a780b93a1a1529da7c3d369",
|
||||||
|
"sha256:dd4d410a76c3762511ae075d50f379ae09551d92525aa5bb307f8343bf7c2c12",
|
||||||
|
"sha256:eb60699de43ba1a1f77363f563bb2c652f7748127ba3a774f7cf2c7804aa0d3d",
|
||||||
|
"sha256:f469f15068cd8351826df4080ffe4cc6377c5bf7d29b5a07b0e717dddb4c7ea2",
|
||||||
|
"sha256:f82c310ddf97b04e1392c33cf9a70909e0ae10a7e2ddc1d64495e3abdc5d19fb",
|
||||||
|
"sha256:fa51ce4aea583b0c6b426f4b0563d3535c1c75986c4373a0987d84d22376585b"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.7'",
|
||||||
|
"version": "==2.0.19"
|
||||||
},
|
},
|
||||||
"sqlalchemy-utils": {
|
"sqlalchemy-utils": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -587,11 +635,11 @@
|
|||||||
},
|
},
|
||||||
"typing-extensions": {
|
"typing-extensions": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:5d8c9dac95c27d20df12fb1d97b9793ab8b2af8a3a525e68c80e21060c161771",
|
"sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36",
|
||||||
"sha256:935ccf31549830cda708b42289d44b6f74084d616a00be651601a4f968e77c82"
|
"sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==4.7.0"
|
"version": "==4.7.1"
|
||||||
},
|
},
|
||||||
"urllib3": {
|
"urllib3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|||||||
@ -18,4 +18,4 @@ class Config(object):
|
|||||||
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
|
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
|
||||||
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
|
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
|
||||||
ADMINS = ['postmaster@wpgcommunity.net']
|
ADMINS = ['postmaster@wpgcommunity.net']
|
||||||
POSTS_PER_PAGE = 10
|
POSTS_PER_PAGE = 15
|
||||||
|
|||||||
@ -0,0 +1,64 @@
|
|||||||
|
"""receipt-id reorganized
|
||||||
|
|
||||||
|
Revision ID: f6f97ed9c053
|
||||||
|
Revises: 4fec22c35530
|
||||||
|
Create Date: 2023-07-02 14:16:06.260479
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'f6f97ed9c053'
|
||||||
|
down_revision = '4fec22c35530'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column('accepted', sa.Boolean(), nullable=False))
|
||||||
|
batch_op.drop_constraint("item_receipt_receipt_fkey")
|
||||||
|
batch_op.alter_column('receipt',
|
||||||
|
existing_type=sa.NUMERIC(precision=24, scale=0),
|
||||||
|
type_=sa.BigInteger(),
|
||||||
|
existing_nullable=False)
|
||||||
|
|
||||||
|
with op.batch_alter_table('receipt', schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column('bonid', sa.Numeric(precision=24, scale=0), nullable=True))
|
||||||
|
|
||||||
|
with op.batch_alter_table('receipt', schema=None) as batch_op:
|
||||||
|
batch_op.execute("UPDATE receipt SET bonid=id;")
|
||||||
|
batch_op.alter_column('id',
|
||||||
|
existing_type=sa.NUMERIC(precision=24, scale=0),
|
||||||
|
type_=sa.BigInteger(),
|
||||||
|
existing_nullable=False,
|
||||||
|
autoincrement=True)
|
||||||
|
batch_op.create_unique_constraint(None, ['bonid'])
|
||||||
|
|
||||||
|
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
|
||||||
|
batch_op.create_foreign_key(None, 'receipt', ['receipt'], ['id'])
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
|
||||||
|
batch_op.alter_column('receipt',
|
||||||
|
existing_type=sa.BigInteger(),
|
||||||
|
type_=sa.NUMERIC(precision=24, scale=0),
|
||||||
|
existing_nullable=False)
|
||||||
|
batch_op.drop_column('accepted')
|
||||||
|
|
||||||
|
with op.batch_alter_table('receipt', schema=None) as batch_op:
|
||||||
|
batch_op.drop_constraint(None, type_='unique')
|
||||||
|
batch_op.alter_column('id',
|
||||||
|
existing_type=sa.BigInteger(),
|
||||||
|
type_=sa.NUMERIC(precision=24, scale=0),
|
||||||
|
existing_nullable=False,
|
||||||
|
autoincrement=True)
|
||||||
|
batch_op.drop_column('bonid')
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
@ -2,7 +2,7 @@ from src import db
|
|||||||
|
|
||||||
|
|
||||||
class Brand(db.Model):
|
class Brand(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||||
name = db.Column(db.String(32), nullable=False)
|
name = db.Column(db.String(32), nullable=False)
|
||||||
|
|
||||||
Item = db.relationship("Item", backref='Brand', lazy='dynamic')
|
Item = db.relationship("Item", backref='Brand', lazy='dynamic')
|
||||||
@ -1,9 +1,9 @@
|
|||||||
from src import db
|
from src import db
|
||||||
from src.models.item_category import item_category
|
from .item_category import item_category
|
||||||
|
|
||||||
|
|
||||||
class Category(db.Model):
|
class Category(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||||
name = db.Column(db.String(32), nullable=False, unique=True)
|
name = db.Column(db.String(32), nullable=False, unique=True)
|
||||||
|
|
||||||
Item = db.relationship("Item", secondary=item_category,
|
Item = db.relationship("Item", secondary=item_category,
|
||||||
@ -2,7 +2,7 @@ from src import db
|
|||||||
|
|
||||||
|
|
||||||
class Establishment(db.Model):
|
class Establishment(db.Model):
|
||||||
id = db.Column(db.BigInteger, primary_key=True)
|
id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
|
||||||
name = db.Column(db.String(64), nullable=False)
|
name = db.Column(db.String(64), nullable=False)
|
||||||
owner = db.Column(db.ForeignKey('user.id'), nullable=False)
|
owner = db.Column(db.ForeignKey('user.id'), nullable=False)
|
||||||
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
from src import db, ma
|
from src import db, ma
|
||||||
from src.models.item_category import item_category
|
from .item_category import item_category
|
||||||
|
|
||||||
|
|
||||||
class Item(db.Model):
|
class Item(db.Model):
|
||||||
@ -2,9 +2,9 @@ from src import db
|
|||||||
|
|
||||||
|
|
||||||
class Receipt(db.Model):
|
class Receipt(db.Model):
|
||||||
id = db.Column(db.Numeric(precision=24, scale=0),
|
id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
|
||||||
primary_key=True, autoincrement=False)
|
|
||||||
date = db.Column(db.Date, nullable=False)
|
date = db.Column(db.Date, nullable=False)
|
||||||
|
bonid = db.Column(db.Numeric(precision=24, scale=0), unique=True)
|
||||||
from_user = db.Column(db.ForeignKey("login_token.token"),
|
from_user = db.Column(db.ForeignKey("login_token.token"),
|
||||||
server_onupdate=db.FetchedValue())
|
server_onupdate=db.FetchedValue())
|
||||||
registered = db.Column(db.Boolean, nullable=False,
|
registered = db.Column(db.Boolean, nullable=False,
|
||||||
@ -1,6 +1,6 @@
|
|||||||
from src import ma
|
from src import ma
|
||||||
from src.models import AmountChange
|
from ..amount_change import AmountChange
|
||||||
from src.models.schemas import ItemSchema
|
from .item import ItemSchema
|
||||||
|
|
||||||
|
|
||||||
class AmountChangeSchema(ma.SQLAlchemySchema):
|
class AmountChangeSchema(ma.SQLAlchemySchema):
|
||||||
@ -1,6 +1,7 @@
|
|||||||
from src import ma
|
from src import ma
|
||||||
from src.models import Bought
|
from ..bought import Bought
|
||||||
from src.models.schemas import LoginTokenSchema, ItemSchema
|
from .login_token import LoginTokenSchema
|
||||||
|
from .item import ItemSchema
|
||||||
|
|
||||||
|
|
||||||
class BoughtSchema(ma.SQLAlchemySchema):
|
class BoughtSchema(ma.SQLAlchemySchema):
|
||||||
@ -1,5 +1,5 @@
|
|||||||
from src import ma
|
from src import ma
|
||||||
from src.models import Brand
|
from ..brand import Brand
|
||||||
|
|
||||||
|
|
||||||
class BrandSchema(ma.SQLAlchemySchema):
|
class BrandSchema(ma.SQLAlchemySchema):
|
||||||
@ -1,6 +1,6 @@
|
|||||||
# from marshmallow import Schema, fields
|
# from marshmallow import Schema, fields
|
||||||
from src import ma
|
from src import ma
|
||||||
from src.models import Category
|
from ..category import Category
|
||||||
|
|
||||||
|
|
||||||
class CategorySchema(ma.SQLAlchemySchema):
|
class CategorySchema(ma.SQLAlchemySchema):
|
||||||
@ -1,6 +1,6 @@
|
|||||||
from src import ma
|
from src import ma
|
||||||
from src.models import Establishment
|
from ..establishment import Establishment
|
||||||
from src.models.schemas import UserSchema
|
from .user import UserSchema
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentSchema(ma.SQLAlchemySchema):
|
class EstablishmentSchema(ma.SQLAlchemySchema):
|
||||||
@ -1,6 +1,7 @@
|
|||||||
from src import ma
|
from src import ma
|
||||||
from src.models import EstablishmentCandidate
|
from ..establishment_candidate import EstablishmentCandidate
|
||||||
from src.models.schemas import EstablishmentSchema, UserSchema
|
from .establishment import EstablishmentSchema
|
||||||
|
from .user import UserSchema
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentCandidateSchema(ma.SQLAlchemySchema):
|
class EstablishmentCandidateSchema(ma.SQLAlchemySchema):
|
||||||
18
backend/models/schemas/item.py
Normal file
18
backend/models/schemas/item.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from src import ma
|
||||||
|
from ..item import Item
|
||||||
|
from .brand import BrandSchema
|
||||||
|
|
||||||
|
class ItemSchema(ma.SQLAlchemySchema):
|
||||||
|
class Meta:
|
||||||
|
model = Item
|
||||||
|
include_fk = True
|
||||||
|
|
||||||
|
id = ma.auto_field()
|
||||||
|
name = ma.auto_field()
|
||||||
|
description = ma.auto_field()
|
||||||
|
Brand = ma.Nested(BrandSchema)
|
||||||
|
PriceChange = ma.List(ma.Nested(lambda: PriceChangeSchema(exclude=("Item",))))
|
||||||
|
AmountChange = ma.List(ma.Nested(lambda: AmountChangeSchema(exclude=("Item",))))
|
||||||
|
|
||||||
|
from .price_change import PriceChangeSchema
|
||||||
|
from .amount_change import AmountChangeSchema
|
||||||
@ -1,6 +1,7 @@
|
|||||||
from src import ma
|
from src import ma
|
||||||
from src.models import LoginToken
|
from ..login_token import LoginToken
|
||||||
from src.models.schemas import EstablishmentSchema, UserSchema
|
from .establishment import EstablishmentSchema
|
||||||
|
from .user import UserSchema
|
||||||
|
|
||||||
|
|
||||||
class LoginTokenSchema(ma.SQLAlchemySchema):
|
class LoginTokenSchema(ma.SQLAlchemySchema):
|
||||||
@ -1,6 +1,6 @@
|
|||||||
from src import ma
|
from src import ma
|
||||||
from src.models import Payment
|
from ..payment import Payment
|
||||||
from src.models.schemas import LoginTokenSchema
|
from .login_token import LoginTokenSchema
|
||||||
|
|
||||||
|
|
||||||
class PaymentSchema(ma.SQLAlchemySchema):
|
class PaymentSchema(ma.SQLAlchemySchema):
|
||||||
@ -1,7 +1,6 @@
|
|||||||
from src import ma
|
from src import ma
|
||||||
from src.models import PriceChange
|
from .item import ItemSchema
|
||||||
from src.models.schemas import ItemSchema
|
from ..price_change import PriceChange
|
||||||
|
|
||||||
|
|
||||||
class PriceChangeSchema(ma.SQLAlchemySchema):
|
class PriceChangeSchema(ma.SQLAlchemySchema):
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1,6 +1,6 @@
|
|||||||
from src import ma
|
from src import ma
|
||||||
from src.models import Receipt
|
from ..receipt import Receipt
|
||||||
from src.models.schemas import LoginTokenSchema
|
from .login_token import LoginTokenSchema
|
||||||
|
|
||||||
|
|
||||||
class ReceiptSchema(ma.SQLAlchemySchema):
|
class ReceiptSchema(ma.SQLAlchemySchema):
|
||||||
@ -10,5 +10,6 @@ class ReceiptSchema(ma.SQLAlchemySchema):
|
|||||||
|
|
||||||
id = ma.auto_field()
|
id = ma.auto_field()
|
||||||
date = ma.auto_field()
|
date = ma.auto_field()
|
||||||
LoginToken = ma.Nested(LoginTokenSchema)
|
bonid = ma.auto_field()
|
||||||
registered = ma.auto_field()
|
registered = ma.auto_field()
|
||||||
|
LoginToken = ma.Nested(LoginTokenSchema)
|
||||||
@ -1,6 +1,7 @@
|
|||||||
from src import ma
|
from src import ma
|
||||||
from src.models import ReceiptItem
|
from ..receipt_item import ReceiptItem
|
||||||
from src.models.schemas import ItemSchema, ReceiptSchema
|
from .item import ItemSchema
|
||||||
|
from .receipt import ReceiptSchema
|
||||||
|
|
||||||
|
|
||||||
class ReceiptItemSchema(ma.SQLAlchemySchema):
|
class ReceiptItemSchema(ma.SQLAlchemySchema):
|
||||||
@ -1,5 +1,5 @@
|
|||||||
from src import ma
|
from src import ma
|
||||||
from src.models import User
|
from ..user import User
|
||||||
|
|
||||||
|
|
||||||
class UserSchema(ma.SQLAlchemySchema):
|
class UserSchema(ma.SQLAlchemySchema):
|
||||||
@ -7,7 +7,7 @@ from werkzeug.security import generate_password_hash, check_password_hash
|
|||||||
|
|
||||||
|
|
||||||
class User(UserMixin, db.Model):
|
class User(UserMixin, db.Model):
|
||||||
id = db.Column(db.BigInteger, primary_key=True)
|
id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
|
||||||
email = db.Column(db.String(255), nullable=False, unique=True)
|
email = db.Column(db.String(255), nullable=False, unique=True)
|
||||||
password_hash = db.Column(db.String(128), nullable=False)
|
password_hash = db.Column(db.String(128), nullable=False)
|
||||||
|
|
||||||
BIN
backend/requirements.txt
Normal file
BIN
backend/requirements.txt
Normal file
Binary file not shown.
@ -1,5 +1,5 @@
|
|||||||
from src import create_app, db
|
from src import create_app, db
|
||||||
from src.models import *
|
from models import *
|
||||||
|
|
||||||
app = create_app()
|
app = create_app()
|
||||||
|
|
||||||
|
|||||||
@ -38,7 +38,7 @@ migrate = Migrate()
|
|||||||
|
|
||||||
|
|
||||||
def create_app(config_class=Config):
|
def create_app(config_class=Config):
|
||||||
app = Flask(__name__)
|
app = Flask(__name__, template_folder="../web/templates", static_folder="../web/static")
|
||||||
app.config.from_object(config_class)
|
app.config.from_object(config_class)
|
||||||
bootstrap.init_app(app)
|
bootstrap.init_app(app)
|
||||||
cors.init_app(app)
|
cors.init_app(app)
|
||||||
@ -65,4 +65,4 @@ def create_app(config_class=Config):
|
|||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
from src.models import *
|
from models import *
|
||||||
@ -1,6 +1,6 @@
|
|||||||
from src import LOGGER
|
from src import LOGGER
|
||||||
from src.api.v1 import bp
|
from src.api.v1 import bp
|
||||||
from src.models.login_token import LoginToken
|
from models.login_token import LoginToken
|
||||||
from src.utils import database_utils
|
from src.utils import database_utils
|
||||||
from flask import abort, request
|
from flask import abort, request
|
||||||
from flask.json import jsonify
|
from flask.json import jsonify
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from src.models.user import User
|
from models.user import User
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import BooleanField, PasswordField, StringField, SubmitField
|
from wtforms import BooleanField, PasswordField, StringField, SubmitField
|
||||||
from wtforms.validators import DataRequired, Email, EqualTo, ValidationError
|
from wtforms.validators import DataRequired, Email, EqualTo, ValidationError
|
||||||
|
|||||||
@ -2,7 +2,7 @@ from src import db
|
|||||||
from . import bp
|
from . import bp
|
||||||
from .email import send_password_reset_email
|
from .email import send_password_reset_email
|
||||||
from .forms import LoginForm, RegistrationForm, ResetPasswordForm, ResetPasswordRequestForm
|
from .forms import LoginForm, RegistrationForm, ResetPasswordForm, ResetPasswordRequestForm
|
||||||
from src.models.user import User
|
from models.user import User
|
||||||
from src.utils.routes_utils import render_custom_template as render_template
|
from src.utils.routes_utils import render_custom_template as render_template
|
||||||
from flask import flash, redirect, request, url_for
|
from flask import flash, redirect, request, url_for
|
||||||
from flask_login import current_user, login_user, logout_user
|
from flask_login import current_user, login_user, logout_user
|
||||||
|
|||||||
@ -3,7 +3,7 @@ from flask_login import current_user, login_required
|
|||||||
from . import bp
|
from . import bp
|
||||||
from .forms import EvaluateCandidateForm
|
from .forms import EvaluateCandidateForm
|
||||||
from src import db, LOGGER
|
from src import db, LOGGER
|
||||||
from src.models import Establishment, EstablishmentCandidate, LoginToken, User
|
from models import Establishment, EstablishmentCandidate, LoginToken, User
|
||||||
from src.utils.routes_utils import render_custom_template as render_template
|
from src.utils.routes_utils import render_custom_template as render_template
|
||||||
from src.utils.database_utils import generate_token
|
from src.utils.database_utils import generate_token
|
||||||
|
|
||||||
|
|||||||
@ -5,8 +5,8 @@ from . import bp
|
|||||||
from .forms import JoinEstablishmentForm
|
from .forms import JoinEstablishmentForm
|
||||||
from .utils import backend_validation
|
from .utils import backend_validation
|
||||||
from src import db
|
from src import db
|
||||||
from src.models import Establishment
|
from models import Establishment
|
||||||
from src.models import EstablishmentCandidate
|
from models import EstablishmentCandidate
|
||||||
from src.utils.routes_utils import render_custom_template as render_template
|
from src.utils.routes_utils import render_custom_template as render_template
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from src.models import Establishment
|
from models import Establishment
|
||||||
|
|
||||||
|
|
||||||
def backend_validation(join_establishment_form):
|
def backend_validation(join_establishment_form):
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
from . import bp
|
from . import bp
|
||||||
from .forms import NewEstablishmentForm
|
from .forms import NewEstablishmentForm
|
||||||
from src import db, LOGGER
|
from src import db, LOGGER
|
||||||
from src.models import Establishment, LoginToken
|
from models import Establishment, LoginToken
|
||||||
from src.utils import database_utils
|
from src.utils import database_utils
|
||||||
from src.utils.routes_utils import render_custom_template as render_template
|
from src.utils.routes_utils import render_custom_template as render_template
|
||||||
from flask import redirect, url_for
|
from flask import redirect, url_for
|
||||||
|
|||||||
@ -3,7 +3,7 @@ from flask.json import jsonify
|
|||||||
from flask_login import current_user, login_required
|
from flask_login import current_user, login_required
|
||||||
from . import bp
|
from . import bp
|
||||||
from src import LOGGER
|
from src import LOGGER
|
||||||
from src.models import Establishment
|
from models import Establishment
|
||||||
from src.utils import view_utils, database_utils
|
from src.utils import view_utils, database_utils
|
||||||
from src.utils.routes_utils import render_custom_template as render_template
|
from src.utils.routes_utils import render_custom_template as render_template
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from src.models import LoginToken
|
from models import LoginToken
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import DateField, FloatField, IntegerField, SelectField, SelectMultipleField, StringField, SubmitField
|
from wtforms import DateField, FloatField, IntegerField, SelectField, SelectMultipleField, StringField, SubmitField
|
||||||
from wtforms.validators import DataRequired, Optional
|
from wtforms.validators import DataRequired, Optional
|
||||||
|
|||||||
@ -3,7 +3,7 @@ from flask_login import current_user, login_required
|
|||||||
from . import bp
|
from . import bp
|
||||||
from .forms import NewPaymentForm
|
from .forms import NewPaymentForm
|
||||||
from src import db, LOGGER
|
from src import db, LOGGER
|
||||||
from src.models import Establishment, Payment
|
from models import Establishment, Payment
|
||||||
from src.utils.routes_utils import render_custom_template as render_template
|
from src.utils.routes_utils import render_custom_template as render_template
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -4,4 +4,8 @@ bp = Blueprint('item', __name__, url_prefix='/item')
|
|||||||
from .new import bp as bp_new_item
|
from .new import bp as bp_new_item
|
||||||
bp.register_blueprint(bp_new_item)
|
bp.register_blueprint(bp_new_item)
|
||||||
from .list import bp as bp_item_list
|
from .list import bp as bp_item_list
|
||||||
bp.register_blueprint(bp_item_list)
|
bp.register_blueprint(bp_item_list)
|
||||||
|
from .details import bp as bp_item_details
|
||||||
|
bp.register_blueprint(bp_item_details)
|
||||||
|
from .update import bp as bp_item_update
|
||||||
|
bp.register_blueprint(bp_item_update)
|
||||||
5
backend/src/item/details/__init__.py
Normal file
5
backend/src/item/details/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
bp = Blueprint('details', __name__, url_prefix='/details')
|
||||||
|
|
||||||
|
from . import routes
|
||||||
14
backend/src/item/details/routes.py
Normal file
14
backend/src/item/details/routes.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# from flask_login import login_required
|
||||||
|
from . import bp
|
||||||
|
from models import Item
|
||||||
|
from models.schemas import ItemSchema
|
||||||
|
from src.utils.routes_utils import render_custom_template as render_template
|
||||||
|
|
||||||
|
@bp.route('/<item>', methods=['GET'])
|
||||||
|
def show_item(item: int):
|
||||||
|
itemobj = Item.query.get_or_404(item)
|
||||||
|
itemschema = ItemSchema().dump(itemobj)
|
||||||
|
itemschema['PriceChange'].sort(key=lambda d: d['date'], reverse=True)
|
||||||
|
itemschema['AmountChange'].sort(key=lambda d: d['date'], reverse=True)
|
||||||
|
print(itemschema)
|
||||||
|
return render_template('item/details/show_item.html', item = itemschema)
|
||||||
@ -1,8 +1,8 @@
|
|||||||
from flask import abort, current_app, request
|
from flask import abort, current_app, request
|
||||||
from flask_login import current_user, login_required
|
from flask_login import current_user, login_required
|
||||||
from . import bp
|
from . import bp
|
||||||
from src.models import Item
|
from models import Item
|
||||||
from src.models.schemas import ItemSchema
|
from models.schemas import ItemSchema
|
||||||
from src.utils.routes_utils import render_custom_template as render_template
|
from src.utils.routes_utils import render_custom_template as render_template
|
||||||
|
|
||||||
@bp.route('/show_items', methods=['GET', 'POST'])
|
@bp.route('/show_items', methods=['GET', 'POST'])
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from src.models import Brand, Category
|
from models import Brand, Category
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import DateField, FloatField, IntegerField, SelectField, SelectMultipleField, StringField, SubmitField
|
from wtforms import DateField, FloatField, IntegerField, SelectField, SelectMultipleField, StringField, SubmitField
|
||||||
from wtforms.validators import DataRequired, Optional
|
from wtforms.validators import DataRequired, Optional
|
||||||
|
|||||||
@ -4,7 +4,7 @@ from flask_login import current_user, login_required
|
|||||||
from . import bp
|
from . import bp
|
||||||
from .forms import NewItemForm
|
from .forms import NewItemForm
|
||||||
from src import db, LOGGER
|
from src import db, LOGGER
|
||||||
from src.models import AmountChange, Brand, Item, PriceChange
|
from models import AmountChange, Brand, Item, PriceChange
|
||||||
from src.utils.routes_utils import render_custom_template as render_template
|
from src.utils.routes_utils import render_custom_template as render_template
|
||||||
|
|
||||||
@bp.route('/new_item', methods=['GET', 'POST'])
|
@bp.route('/new_item', methods=['GET', 'POST'])
|
||||||
|
|||||||
7
backend/src/item/update/__init__.py
Normal file
7
backend/src/item/update/__init__.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
bp = Blueprint('update', __name__, url_prefix='/update')
|
||||||
|
from .price_change import bp as bp_price_change
|
||||||
|
bp.register_blueprint(bp_price_change)
|
||||||
|
from .amount_change import bp as bp_amount_change
|
||||||
|
bp.register_blueprint(bp_amount_change)
|
||||||
5
backend/src/item/update/amount_change/__init__.py
Normal file
5
backend/src/item/update/amount_change/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
bp = Blueprint('amount_change', __name__, url_prefix='/amount_change')
|
||||||
|
|
||||||
|
from . import forms, routes
|
||||||
9
backend/src/item/update/amount_change/forms.py
Normal file
9
backend/src/item/update/amount_change/forms.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import DateField, HiddenField, IntegerField, SubmitField
|
||||||
|
from wtforms.validators import DataRequired
|
||||||
|
|
||||||
|
class NewAmountChangeForm(FlaskForm):
|
||||||
|
id = HiddenField("Product EAN", validators=[DataRequired()], render_kw={"class": "form-control"})
|
||||||
|
date = DateField("Insert Date", validators=[DataRequired()], render_kw={"class": "form-control"})
|
||||||
|
amount_change = IntegerField("Amount", validators=[DataRequired()], render_kw={"class": "form-control"})
|
||||||
|
submit = SubmitField("Submit", render_kw={"class": "btn btn-primary mt-3"})
|
||||||
24
backend/src/item/update/amount_change/routes.py
Normal file
24
backend/src/item/update/amount_change/routes.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from flask import abort, redirect, url_for
|
||||||
|
from flask_login import current_user, login_required
|
||||||
|
from . import bp
|
||||||
|
from .forms import NewAmountChangeForm
|
||||||
|
from src import db, LOGGER
|
||||||
|
from models import AmountChange, Item
|
||||||
|
from src.utils.routes_utils import render_custom_template as render_template
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/<int:item>', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
def amount_change(item: int):
|
||||||
|
if current_user.is_anonymous:
|
||||||
|
abort(403)
|
||||||
|
db_item = Item.query.get_or_404(item)
|
||||||
|
form = NewAmountChangeForm()
|
||||||
|
form.id.data = item
|
||||||
|
if form.validate_on_submit():
|
||||||
|
db_amount_change = AmountChange(
|
||||||
|
item=form.id.data, date=form.date.data, amount=form.amount_change.data)
|
||||||
|
db.session.add(db_amount_change)
|
||||||
|
db.session.commit()
|
||||||
|
return redirect(url_for('main.index'))
|
||||||
|
return render_template('item/update/amount_change/amount_change.html', form=form, item=db_item)
|
||||||
5
backend/src/item/update/price_change/__init__.py
Normal file
5
backend/src/item/update/price_change/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
bp = Blueprint('price_change', __name__, url_prefix='/price_change')
|
||||||
|
|
||||||
|
from . import forms, routes
|
||||||
9
backend/src/item/update/price_change/forms.py
Normal file
9
backend/src/item/update/price_change/forms.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import DateField, HiddenField, FloatField, SubmitField
|
||||||
|
from wtforms.validators import DataRequired
|
||||||
|
|
||||||
|
class NewPriceChangeForm(FlaskForm):
|
||||||
|
id = HiddenField("Product EAN", validators=[DataRequired()], render_kw={"class": "form-control"})
|
||||||
|
date = DateField("Insert Date", validators=[DataRequired()], render_kw={"class": "form-control"})
|
||||||
|
price_change = FloatField("Price", validators=[DataRequired()], render_kw={"class": "form-control"})
|
||||||
|
submit = SubmitField("Submit", render_kw={"class": "btn btn-primary mt-3"})
|
||||||
24
backend/src/item/update/price_change/routes.py
Normal file
24
backend/src/item/update/price_change/routes.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from flask import abort, redirect, url_for
|
||||||
|
from flask_login import current_user, login_required
|
||||||
|
from . import bp
|
||||||
|
from .forms import NewPriceChangeForm
|
||||||
|
from src import db, LOGGER
|
||||||
|
from models import Item, PriceChange
|
||||||
|
from src.utils.routes_utils import render_custom_template as render_template
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/<int:item>', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
def price_change(item: int):
|
||||||
|
if current_user.is_anonymous:
|
||||||
|
abort(403)
|
||||||
|
db_item = Item.query.get_or_404(item)
|
||||||
|
form = NewPriceChangeForm()
|
||||||
|
form.id.data = int(item)
|
||||||
|
if form.validate_on_submit():
|
||||||
|
db_price_change = PriceChange(
|
||||||
|
item=form.id.data, date=form.date.data, price=form.price_change.data)
|
||||||
|
db.session.add(db_price_change)
|
||||||
|
db.session.commit()
|
||||||
|
return redirect(url_for('main.index'))
|
||||||
|
return render_template('item/update/price_change/price_change.html', form=form, item=db_item)
|
||||||
@ -1,14 +0,0 @@
|
|||||||
from src import ma
|
|
||||||
from src.models import Item
|
|
||||||
from src.models.schemas import BrandSchema
|
|
||||||
|
|
||||||
|
|
||||||
class ItemSchema(ma.SQLAlchemySchema):
|
|
||||||
class Meta:
|
|
||||||
model = Item
|
|
||||||
include_fk = True
|
|
||||||
|
|
||||||
id = ma.auto_field()
|
|
||||||
name = ma.auto_field()
|
|
||||||
description = ma.auto_field()
|
|
||||||
Brand = ma.Nested(BrandSchema)
|
|
||||||
5
backend/src/receipts/confirm/__init__.py
Normal file
5
backend/src/receipts/confirm/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
bp = Blueprint('confirm', __name__, url_prefix='/confirm')
|
||||||
|
|
||||||
|
from . import forms, routes
|
||||||
18
backend/src/receipts/confirm/forms.py
Normal file
18
backend/src/receipts/confirm/forms.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import SelectMultipleField, SubmitField, widgets
|
||||||
|
|
||||||
|
class MultiCheckboxField(SelectMultipleField):
|
||||||
|
widget = widgets.ListWidget(prefix_label=False)
|
||||||
|
option_widget = widgets.CheckboxInput()
|
||||||
|
|
||||||
|
class CheckItemsForm(FlaskForm):
|
||||||
|
items = MultiCheckboxField("Items")
|
||||||
|
submit = SubmitField("Submit", render_kw={"class": "btn btn-primary mt-3"})
|
||||||
|
|
||||||
|
#TODO create new() method which loads the form for a specific receipt
|
||||||
|
@classmethod
|
||||||
|
def new(cls, itemArray):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
form = cls()
|
||||||
|
return form
|
||||||
36
backend/src/receipts/confirm/routes.py
Normal file
36
backend/src/receipts/confirm/routes.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
from flask import abort, request, url_for
|
||||||
|
from flask_login import current_user, login_required
|
||||||
|
from . import bp
|
||||||
|
from .forms import CheckItemsForm, UploadReceiptForm
|
||||||
|
from src import db, LOGGER
|
||||||
|
from models.receipt import Receipt
|
||||||
|
from models.login_token import LoginToken
|
||||||
|
from src.utils.pdf_receipt_parser import PDFReceipt
|
||||||
|
from src.utils.routes_utils import render_custom_template as render_template
|
||||||
|
|
||||||
|
PDFDir = "./"
|
||||||
|
|
||||||
|
@bp.route('/<int:receipt_id>', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
def confirm_receipt_items(receipt_id: int):
|
||||||
|
"""Check items from a receipt if they should be accounted for payment.
|
||||||
|
Get those items from the receipt PDF itself."""
|
||||||
|
if "receipt" in request.args:
|
||||||
|
receipt_details = Receipt.query.get(request.args['receipt'])
|
||||||
|
if current_user.is_anonymous and current_user.id == receipt_details.LoginToken.Establishment.owner:
|
||||||
|
receipt = PDFReceipt._getPDFReceiptFromFile(PDFDir + f"{receipt.date}_{receipt.id}.pdf")
|
||||||
|
form = CheckItemsForm()
|
||||||
|
# TODO: Precheck if items are already in database. If yes, check if item is present only once or multiple
|
||||||
|
# times and provide dropdown menu if necessary. If not, provide input field.
|
||||||
|
temp_choices = []
|
||||||
|
for item in receipt.items:
|
||||||
|
match item:
|
||||||
|
case {"itemname": itemname, "price": price}:
|
||||||
|
temp_choices.append((itemname.replace(" ", "_"), f"{itemname, price}"))
|
||||||
|
case {"itemname": itemname, "price": price, "amount": amount}:
|
||||||
|
temp_choices.append((itemname.replace(" ", "_"), f"{itemname}, {price} * {amount}"))
|
||||||
|
form.choices = temp_choices
|
||||||
|
if form.validate_on_submit():
|
||||||
|
pass # TODO
|
||||||
|
return render_template("receipts/confirm_items.html")
|
||||||
|
abort(403)
|
||||||
@ -1,10 +1,11 @@
|
|||||||
from flask import abort, request, url_for
|
from flask import abort, request, url_for
|
||||||
from flask_login import current_user, login_required
|
from flask_login import current_user, login_required
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
from . import bp
|
from . import bp
|
||||||
from .forms import CheckItemsForm, UploadReceiptForm
|
from .forms import UploadReceiptForm
|
||||||
from src import db, LOGGER
|
from src import db, LOGGER
|
||||||
from src.models.receipt import Receipt
|
from models.receipt import Receipt
|
||||||
from src.models.login_token import LoginToken
|
from models.login_token import LoginToken
|
||||||
from src.utils.pdf_receipt_parser import PDFReceipt
|
from src.utils.pdf_receipt_parser import PDFReceipt
|
||||||
from src.utils.routes_utils import render_custom_template as render_template
|
from src.utils.routes_utils import render_custom_template as render_template
|
||||||
|
|
||||||
@ -20,43 +21,15 @@ def upload_receipt():
|
|||||||
if LoginToken.query.filter_by(establishment=request.args['establishment'], user=current_user.id).first():
|
if LoginToken.query.filter_by(establishment=request.args['establishment'], user=current_user.id).first():
|
||||||
form = UploadReceiptForm()
|
form = UploadReceiptForm()
|
||||||
LOGGER.debug(form.pdfReceipt.data)
|
LOGGER.debug(form.pdfReceipt.data)
|
||||||
if form.is_submitted():
|
|
||||||
LOGGER.debug("submitted")
|
|
||||||
if form.validate():
|
|
||||||
LOGGER.debug("valid")
|
|
||||||
else:
|
|
||||||
LOGGER.debug(form.errors)
|
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
receipt = PDFReceipt(form.pdfReceipt.data)
|
receipt = PDFReceipt(form.pdfReceipt.data)
|
||||||
dbReceipt = Receipt(id = receipt.id, date = receipt.date,
|
dbReceipt = Receipt(id = receipt.id, date = receipt.date,
|
||||||
from_user = LoginToken.query.filter_by(establishment=request.args['establishment'], user=current_user.id).first().token)
|
from_user = LoginToken.query.filter_by(establishment=request.args['establishment'], user=current_user.id).first().token)
|
||||||
form.pdfReceipt.data.save(PDFDir + f"{str(receipt.date)}_{receipt.id}.pdf")
|
form.pdfReceipt.data.save(PDFDir + secure_filename(f"{str(receipt.date)}_{receipt.id}.pdf"))
|
||||||
db.session.add(dbReceipt)
|
db.session.add(dbReceipt)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return receipt.text.replace("\n", "<br>")
|
return receipt.text.replace("\n", "<br>")
|
||||||
|
else:
|
||||||
|
LOGGER.debug(form.errors)
|
||||||
return render_template("receipts/upload.html", form = form)
|
return render_template("receipts/upload.html", form = form)
|
||||||
abort(403)
|
|
||||||
|
|
||||||
@bp.route('/confirm_receipt', methods=['GET', 'POST'])
|
|
||||||
@login_required
|
|
||||||
def confirm_receipt_items():
|
|
||||||
"""Check items from a receipt if they should be accounted for payment."""
|
|
||||||
if "receipt" in request.args:
|
|
||||||
receipt_details = Receipt.query.get(request.args['receipt'])
|
|
||||||
if current_user.is_anonymous and current_user.id == receipt_details.LoginToken.Establishment.owner:
|
|
||||||
receipt = PDFReceipt._getPDFReceiptFromFile(PDFDir + f"{receipt.date}_{receipt.id}.pdf")
|
|
||||||
form = CheckItemsForm()
|
|
||||||
# TODO: Precheck if items are already in database. If yes, check if item is present only once or multiple
|
|
||||||
# times and provide dropdown menu if necessary. If not, provide input field.
|
|
||||||
temp_choices = []
|
|
||||||
for item in receipt.items:
|
|
||||||
match item:
|
|
||||||
case {"itemname": itemname, "price": price}:
|
|
||||||
temp_choices.append((itemname.replace(" ", "_"), f"{itemname, price}"))
|
|
||||||
case {"itemname": itemname, "price": price, "amount": amount}:
|
|
||||||
temp_choices.append((itemname.replace(" ", "_"), f"{itemname}, {price} * {amount}"))
|
|
||||||
form.choices = temp_choices
|
|
||||||
if form.validate_on_submit():
|
|
||||||
pass # TODO
|
|
||||||
return render_template("receipts/confirm_items.html")
|
|
||||||
abort(403)
|
abort(403)
|
||||||
5
backend/src/receipts/upload/__init__.py
Normal file
5
backend/src/receipts/upload/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
bp = Blueprint('upload', __name__, url_prefix='/upload')
|
||||||
|
|
||||||
|
from . import forms, routes
|
||||||
7
backend/src/receipts/upload/forms.py
Normal file
7
backend/src/receipts/upload/forms.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from flask_wtf.file import FileAllowed, FileField, FileRequired
|
||||||
|
from wtforms import SubmitField
|
||||||
|
|
||||||
|
class UploadReceiptForm(FlaskForm):
|
||||||
|
pdfReceipt = FileField("PDF", validators=[FileRequired(), FileAllowed(["pdf"], "Invalid Format, must be .pdf")])
|
||||||
|
submit = SubmitField("Submit", render_kw={"class": "btn btn-primary mt-3"})
|
||||||
37
backend/src/receipts/upload/routes.py
Normal file
37
backend/src/receipts/upload/routes.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
from flask import abort, request, url_for
|
||||||
|
from flask_login import current_user, login_required
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
|
from . import bp
|
||||||
|
from .forms import UploadReceiptForm
|
||||||
|
from src import db, LOGGER
|
||||||
|
from models.receipt import Receipt
|
||||||
|
from models.login_token import LoginToken
|
||||||
|
from src.utils.pdf_receipt_parser import PDFReceipt
|
||||||
|
from src.utils.routes_utils import render_custom_template as render_template
|
||||||
|
|
||||||
|
PDFDir = "./"
|
||||||
|
# TODO überarbeiten. PDFs müssen in der Datenbank eine eigene ID bekommen.
|
||||||
|
# Quittungen haben eine USt.-ID. Die muss als Unique Key in der Datenbank
|
||||||
|
# hinterlegt sein.
|
||||||
|
# Die laufende ID ist zum abspeichern der PDFs gedacht.
|
||||||
|
@bp.route('/<int:establishment>', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
def upload_receipt(establishment: int):
|
||||||
|
"""Upload of a receipt."""
|
||||||
|
if current_user.is_anonymous:
|
||||||
|
abort(403)
|
||||||
|
if LoginToken.query.filter_by(establishment=request.args['establishment'], user=current_user.id).first():
|
||||||
|
form = UploadReceiptForm()
|
||||||
|
LOGGER.debug(form.pdfReceipt.data)
|
||||||
|
if form.validate_on_submit():
|
||||||
|
receipt = PDFReceipt(form.pdfReceipt.data)
|
||||||
|
dbReceipt = Receipt(id = receipt.id, date = receipt.date,
|
||||||
|
from_user = LoginToken.query.filter_by(establishment=request.args['establishment'], user=current_user.id).first().token)
|
||||||
|
form.pdfReceipt.data.save(PDFDir + secure_filename(f"{str(receipt.date)}_{receipt.id}.pdf"))
|
||||||
|
db.session.add(dbReceipt)
|
||||||
|
db.session.commit()
|
||||||
|
return receipt.text.replace("\n", "<br>")
|
||||||
|
else:
|
||||||
|
LOGGER.debug(form.errors)
|
||||||
|
return render_template("receipts/upload.html", form = form)
|
||||||
|
abort(403)
|
||||||
@ -1,29 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% import 'bootstrap/wtf.html' as wtf %}
|
|
||||||
|
|
||||||
{% block app_content %}
|
|
||||||
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">#</th>
|
|
||||||
<th scope="col">Name</th>
|
|
||||||
<th scope="col">Beschreibung</th>
|
|
||||||
<th scope="col">Marke</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for item in items %}
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{{ item.id }}</th>
|
|
||||||
<td>{{ item.name }}</td>
|
|
||||||
<td>{{ item.description }}</td>
|
|
||||||
<td>{{ item.Brand.name }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{% with pagination_object=items %}
|
|
||||||
{% include "utils/general/_page_navigation.html" %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endblock %}
|
|
||||||
@ -8,7 +8,7 @@ from sqlalchemy.dialects.postgresql import insert
|
|||||||
from string import ascii_letters, digits
|
from string import ascii_letters, digits
|
||||||
from .view_utils import bought_with_prices as bwp
|
from .view_utils import bought_with_prices as bwp
|
||||||
from src import db, LOGGER
|
from src import db, LOGGER
|
||||||
from src.models import Bought, Establishment, Item, LoginToken, User
|
from models import Bought, Establishment, Item, LoginToken, User
|
||||||
|
|
||||||
|
|
||||||
def insert_bought_items(token: str, dates: list[dict[str: any]]):
|
def insert_bought_items(token: str, dates: list[dict[str: any]]):
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
from sqlalchemy_utils import create_view
|
from sqlalchemy_utils import create_view
|
||||||
from src import db, LOGGER
|
from src import db, LOGGER
|
||||||
from src.models.amount_change import AmountChange
|
from models.amount_change import AmountChange
|
||||||
from src.models.bought import Bought
|
from models.bought import Bought
|
||||||
from src.models.price_change import PriceChange
|
from models.price_change import PriceChange
|
||||||
|
|
||||||
def group_results(results: tuple) -> list:
|
def group_results(results: tuple) -> list:
|
||||||
result_list = []
|
result_list = []
|
||||||
|
|||||||
79
backend/web/templates/item/details/show_item.html
Normal file
79
backend/web/templates/item/details/show_item.html
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
|
||||||
|
<table class="table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">EAN</th>
|
||||||
|
<th scope="row" class="w-75">{{ item.id }}</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Marke</td>
|
||||||
|
<td>{{ item.Brand.name }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Bezeichnung auf dem Kassenzettel</td>
|
||||||
|
<td>{{ item.name }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Beschreibung</td>
|
||||||
|
<td>{{ item.description }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{% if item.PriceChange %}
|
||||||
|
Momentaner Preis pro Scan</td>
|
||||||
|
<td>€
|
||||||
|
{% if item.AmountChange %}
|
||||||
|
{{ ((item.PriceChange[0].price / item.AmountChange[0].amount)/100)|round(2, 'ceil') }}
|
||||||
|
{% else %}
|
||||||
|
{{ (item.PriceChange[0].price)/100 }}
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
Kein Preis vorhanden.
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<br>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Preishistorie</th>
|
||||||
|
<td class="w-75">
|
||||||
|
<a href="{{ url_for('item.update.price_change.price_change', item = item.id) }}">Einfügen</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for price in item.PriceChange %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ price.date }}</td>
|
||||||
|
<td>€ {{ price.price/100 }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<br>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Mengenhistorie</th>
|
||||||
|
<td class="w-75">
|
||||||
|
<a href="{{ url_for('item.update.amount_change.amount_change', item = item.id) }}">Einfügen</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for amount in item.AmountChange %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ amount.date }}</td>
|
||||||
|
<td>{{ amount.amount }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
||||||
32
backend/web/templates/item/list/show_items.html
Normal file
32
backend/web/templates/item/list/show_items.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover table-sm">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="w-0">#</th>
|
||||||
|
<th scope="col" class="w-0">Name</th>
|
||||||
|
<th scope="col" class="w-50">Beschreibung</th>
|
||||||
|
<th scope="col">Marke</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for item in items %}
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<a href="{{ url_for('item.details.show_item', item=item.id) }}">{{ item.id }}</a>
|
||||||
|
</th>
|
||||||
|
<td>{{ item.name }}</td>
|
||||||
|
<td>{{ item.description }}</td>
|
||||||
|
<td>{{ item.Brand.name }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% with pagination_object=items %}
|
||||||
|
{% include "utils/general/_page_navigation.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endblock %}
|
||||||
1
backend/web/templates/item/update/_item_to_change.html
Normal file
1
backend/web/templates/item/update/_item_to_change.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<h3>({{ item.id }}) {{ item.name }}</h3>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user