Compare commits

...

21 Commits

Author SHA1 Message Date
d10706e301 Merge pull request 'Komplettes Profil zurückgeben' (#19) from feature-return-entire-profile into main
Reviewed-on: kompetenzinventar/ki-backend#19
2021-06-27 09:44:59 +02:00
fc01bec163
return full profile response 2021-06-26 12:16:14 +02:00
3bd9b03002
add yapf config file 2021-06-26 11:40:29 +02:00
ace1b1ed85
seed entire profile 2021-06-26 10:51:39 +02:00
09669cf369
update readme 2021-06-26 09:49:09 +02:00
5dc0f7153d Merge pull request 'Behebung Fehler in 1:n Verknüpfung Benutzer ↔ Token' (#17) from fix-login into main
Reviewed-on: kompetenzinventar/ki-backend#17
2021-06-23 12:45:44 +02:00
5d259635a2
fix db error on second login 2021-06-22 17:52:22 +02:00
Frank Lanitz
b09b072261 Merge remote-tracking branch 'remotes/origin/feature-cors' 2021-06-22 09:23:34 +02:00
7e8c5a7de0 Merge pull request 'pre-commit mit flake8' (#14) from feature-pre-commit into main
Reviewed-on: kompetenzinventar/ki-backend#14
2021-06-22 09:13:06 +02:00
a9f9c36eda
add cors 2021-06-21 22:21:25 +02:00
a349eff9b0
only build prs and main 2021-06-21 22:00:33 +02:00
4d88ee8b77
add pre-commit 2021-06-21 21:45:58 +02:00
9e48953fc3 Merge pull request 'Logging' (#7) from feature-logging into main
Reviewed-on: kompetenzinventar/ki-backend#7
2021-06-21 21:44:33 +02:00
d96dfa8800
fix code style 2021-06-21 21:22:39 +02:00
Frank Lanitz
2f0dd2ab9f users/login: Provider user_id together with token 2021-06-21 18:41:35 +02:00
ea7b6391c1 Merge pull request 'Add yapf to Pipenv environment' (#6) from add-yapf into main
Reviewed-on: kompetenzinventar/ki-backend#6
2021-06-21 17:38:05 +02:00
3dcba71a6d
add logging 2021-06-21 17:35:28 +02:00
Frank Lanitz
cbf3002b93 Reformat source code 2021-06-21 17:28:57 +02:00
Frank Lanitz
59de00527d Ignore *.pyc-files for git 2021-06-21 16:12:54 +02:00
Frank Lanitz
6d4f933585 Add yapf to Pipfile 2021-06-21 16:09:37 +02:00
Frank Lanitz
1390dfa8e6 Add usage of yapf to README 2021-06-21 16:09:26 +02:00
20 changed files with 491 additions and 119 deletions

View File

@ -4,7 +4,7 @@ type: docker
name: default name: default
steps: steps:
- name: qa - name: qa_main
image: python:3.8-alpine image: python:3.8-alpine
commands: commands:
- apk add --no-cache gcc g++ musl-dev python3-dev - apk add --no-cache gcc g++ musl-dev python3-dev
@ -12,3 +12,18 @@ steps:
- pipenv install --dev - pipenv install --dev
- pipenv run flake8 - pipenv run flake8
- pipenv run python -m unittest discover ki - pipenv run python -m unittest discover ki
when:
branch:
- main
- name: qa_pr
image: python:3.8-alpine
commands:
- apk add --no-cache gcc g++ musl-dev python3-dev
- pip3 install pipenv
- pipenv install --dev
- pipenv run flake8
- pipenv run python -m unittest discover ki
when:
event:
- pull_request

View File

@ -1,5 +1,4 @@
[flake8] [flake8]
max-line-length = 120 max-line-length = 120
exclude = extend-exclude =
.git,
migrations migrations

1
.gitignore vendored
View File

@ -1 +1,2 @@
/.env /.env
*.pyc

14
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,14 @@
- repo: local
hooks:
- id: flake8
name: flake8
entry: flake8
language: system
files: ^.*\.py$
exclude: ^(migrations).*$
- id: unittest
name: unittest
entry: python -m unittest discover ki
language: system
exclude: .*
always_run: true

3
.style.yapf Normal file
View File

@ -0,0 +1,3 @@
[style]
based_on_style = pep8
allow_split_before_dict_value = 0

View File

@ -11,9 +11,12 @@ flask-sqlalchemy = "~=2.5.1"
sqlalchemy = "~=1.4.18" sqlalchemy = "~=1.4.18"
waitress = "~=2.0.0" waitress = "~=2.0.0"
pyyaml = "~=5.4.1" pyyaml = "~=5.4.1"
flask-cors = "~=3.0.10"
[dev-packages] [dev-packages]
flake8 = "~=3.9.2" flake8 = "~=3.9.2"
yapf = "~=0.31.0"
pre-commit = "~=2.13.0"
[requires] [requires]
python_version = "3.8" python_version = "3.8"

135
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "439b60cb87b0180f0b78c531085f9bbeef7685ef038256f80b0a8123e7d144e6" "sha256": "11a821c6c1f072dcf7c39a020056fa289b7a5283aa33d96e2ed6860fbc023fa4"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -21,7 +21,6 @@
"sha256:a21fedebb3fb8f6bbbba51a11114f08c78709377051384c9c5ead5705ee93a51", "sha256:a21fedebb3fb8f6bbbba51a11114f08c78709377051384c9c5ead5705ee93a51",
"sha256:e78be5b919f5bb184e3e0e2dd1ca986f2362e29a2bc933c446fe89f39dbe4e9c" "sha256:e78be5b919f5bb184e3e0e2dd1ca986f2362e29a2bc933c446fe89f39dbe4e9c"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==1.6.5" "version": "==1.6.5"
}, },
"click": { "click": {
@ -29,7 +28,6 @@
"sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a", "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a",
"sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6" "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"
], ],
"markers": "python_version >= '3.6'",
"version": "==8.0.1" "version": "==8.0.1"
}, },
"flask": { "flask": {
@ -40,6 +38,14 @@
"index": "pypi", "index": "pypi",
"version": "==2.0.1" "version": "==2.0.1"
}, },
"flask-cors": {
"hashes": [
"sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438",
"sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de"
],
"index": "pypi",
"version": "==3.0.10"
},
"flask-migrate": { "flask-migrate": {
"hashes": [ "hashes": [
"sha256:4d42e8f861d78cb6e9319afcba5bf76062e5efd7784184dd2a1cccd9de34a702", "sha256:4d42e8f861d78cb6e9319afcba5bf76062e5efd7784184dd2a1cccd9de34a702",
@ -116,7 +122,6 @@
"sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c", "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c",
"sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0" "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0"
], ],
"markers": "python_version >= '3.6'",
"version": "==2.0.1" "version": "==2.0.1"
}, },
"jinja2": { "jinja2": {
@ -124,7 +129,6 @@
"sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4", "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4",
"sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4" "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"
], ],
"markers": "python_version >= '3.6'",
"version": "==3.0.1" "version": "==3.0.1"
}, },
"mako": { "mako": {
@ -132,7 +136,6 @@
"sha256:17831f0b7087c313c0ffae2bcbbd3c1d5ba9eeac9c38f2eb7b50e8c99fe9d5ab", "sha256:17831f0b7087c313c0ffae2bcbbd3c1d5ba9eeac9c38f2eb7b50e8c99fe9d5ab",
"sha256:aea166356da44b9b830c8023cd9b557fa856bd8b4035d6de771ca027dfc5cc6e" "sha256:aea166356da44b9b830c8023cd9b557fa856bd8b4035d6de771ca027dfc5cc6e"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.1.4" "version": "==1.1.4"
}, },
"markupsafe": { "markupsafe": {
@ -172,7 +175,6 @@
"sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51", "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51",
"sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872" "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"
], ],
"markers": "python_version >= '3.6'",
"version": "==2.0.1" "version": "==2.0.1"
}, },
"python-dateutil": { "python-dateutil": {
@ -180,7 +182,6 @@
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.8.1" "version": "==2.8.1"
}, },
"python-dotenv": { "python-dotenv": {
@ -241,7 +242,6 @@
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.16.0" "version": "==1.16.0"
}, },
"sqlalchemy": { "sqlalchemy": {
@ -293,11 +293,38 @@
"sha256:1de1db30d010ff1af14a009224ec49ab2329ad2cde454c8a708130642d579c42", "sha256:1de1db30d010ff1af14a009224ec49ab2329ad2cde454c8a708130642d579c42",
"sha256:6c1ec500dcdba0baa27600f6a22f6333d8b662d22027ff9f6202e3367413caa8" "sha256:6c1ec500dcdba0baa27600f6a22f6333d8b662d22027ff9f6202e3367413caa8"
], ],
"markers": "python_version >= '3.6'",
"version": "==2.0.1" "version": "==2.0.1"
} }
}, },
"develop": { "develop": {
"appdirs": {
"hashes": [
"sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41",
"sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"
],
"version": "==1.4.4"
},
"cfgv": {
"hashes": [
"sha256:9e600479b3b99e8af981ecdfc80a0296104ee610cab48a5ae4ffd0b668650eb1",
"sha256:b449c9c6118fe8cca7fa5e00b9ec60ba08145d281d52164230a69211c5d597a1"
],
"version": "==3.3.0"
},
"distlib": {
"hashes": [
"sha256:106fef6dc37dd8c0e2c0a60d3fca3e77460a48907f335fa28420463a6f799736",
"sha256:23e223426b28491b1ced97dc3bbe183027419dfc7982b4fa2f05d5f3ff10711c"
],
"version": "==0.3.2"
},
"filelock": {
"hashes": [
"sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59",
"sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"
],
"version": "==3.0.12"
},
"flake8": { "flake8": {
"hashes": [ "hashes": [
"sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b", "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b",
@ -306,6 +333,13 @@
"index": "pypi", "index": "pypi",
"version": "==3.9.2" "version": "==3.9.2"
}, },
"identify": {
"hashes": [
"sha256:18d0c531ee3dbc112fa6181f34faa179de3f57ea57ae2899754f16a7e0ff6421",
"sha256:5b41f71471bc738e7b586308c3fca172f78940195cb3bf6734c1e66fdac49306"
],
"version": "==2.2.10"
},
"mccabe": { "mccabe": {
"hashes": [ "hashes": [
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
@ -313,12 +347,26 @@
], ],
"version": "==0.6.1" "version": "==0.6.1"
}, },
"nodeenv": {
"hashes": [
"sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b",
"sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"
],
"version": "==1.6.0"
},
"pre-commit": {
"hashes": [
"sha256:764972c60693dc668ba8e86eb29654ec3144501310f7198742a767bec385a378",
"sha256:b679d0fddd5b9d6d98783ae5f10fd0c4c59954f375b70a58cbe1ce9bcf9809a4"
],
"index": "pypi",
"version": "==2.13.0"
},
"pycodestyle": { "pycodestyle": {
"hashes": [ "hashes": [
"sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068", "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068",
"sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef" "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.7.0" "version": "==2.7.0"
}, },
"pyflakes": { "pyflakes": {
@ -326,8 +374,71 @@
"sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3", "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3",
"sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db" "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.3.1" "version": "==2.3.1"
},
"pyyaml": {
"hashes": [
"sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf",
"sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696",
"sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393",
"sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77",
"sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922",
"sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5",
"sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8",
"sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10",
"sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc",
"sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018",
"sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e",
"sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253",
"sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347",
"sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183",
"sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541",
"sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb",
"sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185",
"sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc",
"sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db",
"sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa",
"sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46",
"sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122",
"sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b",
"sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63",
"sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df",
"sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc",
"sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247",
"sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6",
"sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"
],
"index": "pypi",
"version": "==5.4.1"
},
"six": {
"hashes": [
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
"version": "==1.16.0"
},
"toml": {
"hashes": [
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
],
"version": "==0.10.2"
},
"virtualenv": {
"hashes": [
"sha256:14fdf849f80dbb29a4eb6caa9875d476ee2a5cf76a5f5415fa2f1606010ab467",
"sha256:2b0126166ea7c9c3661f5b8e06773d28f83322de7a3ff7d06f0aed18c9de6a76"
],
"version": "==20.4.7"
},
"yapf": {
"hashes": [
"sha256:408fb9a2b254c302f49db83c59f9aa0b4b0fd0ec25be3a5c51181327922ff63d",
"sha256:e3a234ba8455fe201eaa649cdac872d590089a18b661e39bbac7020978dd9c2e"
],
"index": "pypi",
"version": "==0.31.0"
} }
} }
} }

View File

@ -9,6 +9,7 @@
- Python 3.8 - Python 3.8
- [Pipenv](https://github.com/pypa/pipenv) - [Pipenv](https://github.com/pypa/pipenv)
### Entwicklungsumgebung aufbauen und starten ### Entwicklungsumgebung aufbauen und starten
Ggf. vorher aufräumen Ggf. vorher aufräumen
@ -21,25 +22,39 @@ rm data/ki.sqlite
cp env.dev .env cp env.dev .env
pipenv install --dev pipenv install --dev
pipenv shell pipenv shell
export FLASK_APP=app.py
flask db upgrade flask db upgrade
flask seed flask seed --dev
flask run flask run
``` ```
http://localhost:5000/ http://localhost:5000/
### Tests ausführen ### pre-commit einrichten
Damit mensch nicht verpeilt kaputten Code Style zu commiten,
kann pre-commit benutzt werden. Einmal im Virtualenv ausführen:
```
pre-commit install
```
### `alembic` Befehle
`alembic` ist über [Flask-Migrate](https://flask-migrate.readthedocs.io/en/latest/index.html) eingebunden.
Es wird über `flask db ...` aufgerufen.
### QA
``` ```
python -m unittest discover ki python -m unittest discover ki
```
# Code formatieren
yapf -i --recursive ki/
### Linting # Code-Style prüfen
```
flake8 flake8
``` ```

14
app.py
View File

@ -1,17 +1,27 @@
import logging
import os import os
from dotenv import load_dotenv, find_dotenv from dotenv import load_dotenv, find_dotenv
from flask import Flask from flask import Flask
from flask_cors import CORS
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate from flask_migrate import Migrate
load_dotenv(find_dotenv()) load_dotenv(find_dotenv())
loglevel = os.getenv("KI_LOGLEVEL", logging.WARNING)
loglevel = int(loglevel)
logging.basicConfig(level=loglevel)
logging.debug("Hello from KI")
app = Flask(__name__) app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv("SQLALCHEMY_DATABASE_URI") app.config["SQLALCHEMY_DATABASE_URI"] = os.getenv("SQLALCHEMY_DATABASE_URI")
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["KI_DATA_DIR"] = os.path.dirname(__file__) + "/data" app.config["KI_DATA_DIR"] = os.path.dirname(__file__) + "/data"
app.config["KI_AUTH"] = os.getenv("KI_AUTH") app.config["KI_AUTH"] = os.getenv("KI_AUTH")
app.config["CORS_ORIGINS"] = os.getenv("CORS_ORIGINS", "*")
CORS(app)
db = SQLAlchemy(app) db = SQLAlchemy(app)
migrate = Migrate(app, db) migrate = Migrate(app, db)

View File

@ -1,14 +0,0 @@
---
kind: pipeline
type: docker
name: default
steps:
- name: qa
image: python3.8-alpine
commands:
- apk add --no-cache gcc g++ musl-dev python3-dev
- pip3 install pipenv
- pipenv install --system
- flake8
- python -m unittest discover ki

10
env.dev
View File

@ -1,3 +1,11 @@
SQLALCHEMY_DATABASE_URI = 'sqlite:///data/ki.sqlite' SQLALCHEMY_DATABASE_URI=sqlite:///data/ki.sqlite
CORS_ORIGINS=*
FLASK_APP=app.py
FLASK_ENV=development
KI_AUTH=file KI_AUTH=file
# 10 = debug
KI_LOGLEVEL=10

1
ki/actions/__init__.py Normal file
View File

@ -0,0 +1 @@
from ki.actions.seed import seed # noqa

83
ki/actions/seed.py Normal file
View File

@ -0,0 +1,83 @@
import csv
import logging
from app import app, db
from ki.models import Address, Contact, ContactType, Language, Skill, Profile, ProfileLanguage, ProfileSkill, User
def seed(dev: bool):
skill_seed_file_path = app.config["KI_DATA_DIR"] + "/seed_data/skills.csv"
logging.info("importing skills")
with open(skill_seed_file_path) as skills_file:
skills_csv_reader = csv.DictReader(skills_file)
for skill in skills_csv_reader:
id = int(skill["id"])
db_skill = Skill.query.get(id)
if db_skill is None:
db.session.add(Skill(id=int(skill["id"]), name=skill["name"]))
logging.info("importing languages")
iso_seed_file_path = app.config["KI_DATA_DIR"] + "/seed_data/iso_639_1.csv"
with open(iso_seed_file_path) as iso_file:
iso_csv_reader = csv.DictReader(iso_file)
for iso in iso_csv_reader:
id = iso["639-1"]
db_language = Language.query.get(id)
if db_language is None:
db.session.add(Language(id=iso["639-1"], name=iso["Sprache"]))
if dev:
logging.info("seeding peter :)")
peter = User(auth_id="peter")
db.session.add(peter)
peters_profile = Profile(nickname="peternichtlustig",
pronouns="Herr Dr. Dr.",
volunteerwork="Gartenverein",
freetext="Ich mag Kaffee",
user=peter)
db.session.add(peters_profile)
matrix_type = ContactType(name="Matrix")
db.session.add(matrix_type)
matrix_contact = Contact(profile=peters_profile,
contacttype=matrix_type,
content="@peter:wtf-eg.de")
db.session.add(matrix_contact)
peters_address = Address(name="Peter Nichtlustig",
street="Waldweg",
house_number="23i",
additional="Hinterhaus",
postcode="13337",
city="Bielefeld",
country="Deutschland",
profile=peters_profile)
db.session.add(peters_address)
peters_python_skill = ProfileSkill(profile=peters_profile,
skill_id=3,
level=5)
db.session.add(peters_python_skill)
peter_de = ProfileLanguage(profile=peters_profile,
language_id="de",
level=5)
db.session.add(peter_de)
peter_fr = ProfileLanguage(profile=peters_profile,
language_id="fr",
level=3)
db.session.add(peter_fr)
db.session.commit()

View File

@ -1,39 +1,10 @@
import csv import click
from ki.models import Language, Skill from app import app
from app import app, db from ki.actions import seed
def seed():
skill_seed_file_path = app.config["KI_DATA_DIR"] + "/seed_data/skills.csv"
print("importing skills")
with open(skill_seed_file_path) as skills_file:
skills_csv_reader = csv.DictReader(skills_file)
for skill in skills_csv_reader:
id = int(skill["id"])
db_skill = Skill.query.get(id)
if db_skill is None:
db.session.add(Skill(id=int(skill["id"]), name=skill["name"]))
iso_seed_file_path = app.config["KI_DATA_DIR"] + "/seed_data/iso_639_1.csv"
with open(iso_seed_file_path) as iso_file:
iso_csv_reader = csv.DictReader(iso_file)
for iso in iso_csv_reader:
id = iso["639-1"]
db_language = Language.query.get(id)
if db_language is None:
db.session.add(Language(id=iso["639-1"], name=iso["Sprache"]))
db.session.commit()
@app.cli.command("seed") @app.cli.command("seed")
def seed_command(): @click.option("--dev", is_flag=True)
seed() def seed_command(dev):
seed(dev)

View File

@ -13,7 +13,7 @@ class User(db.Model):
auth_id = Column(String(50), nullable=False, unique=True) auth_id = Column(String(50), nullable=False, unique=True)
profile_id = Column(Integer, ForeignKey("profile.id"), nullable=True) profile_id = Column(Integer, ForeignKey("profile.id"), nullable=True)
tokens = relationship("Token", uselist=False, back_populates="user") tokens = relationship("Token", back_populates="user")
profile = relationship("Profile", back_populates="user") profile = relationship("Profile", back_populates="user")
def to_dict(self): def to_dict(self):
@ -42,10 +42,17 @@ class Profile(db.Model):
def to_dict(self): def to_dict(self):
return { return {
"user_id": self.user.id,
"nickname": self.nickname, "nickname": self.nickname,
"pronouns": self.pronouns, "pronouns": self.pronouns,
"volunteerwork": self.volunteerwork, "volunteerwork": self.volunteerwork,
"freetext": self.freetext "freetext": self.freetext,
"address": self.address.to_dict(),
"contacts": list(
map(lambda contact: contact.to_dict(), self.contacts)),
"skills": list(map(lambda skill: skill.to_dict(), self.skills)),
"languages": list(
map(lambda language: language.to_dict(), self.languages))
} }
@ -58,6 +65,9 @@ class Token(db.Model):
user = relationship("User", back_populates="tokens") user = relationship("User", back_populates="tokens")
def to_dict(self):
return {"user_id": self.user_id, "token": self.token}
class Contact(db.Model): class Contact(db.Model):
__tablename__ = "contact" __tablename__ = "contact"
@ -71,6 +81,14 @@ class Contact(db.Model):
contacttype = relationship("ContactType") contacttype = relationship("ContactType")
content = Column(String(200), nullable=False) content = Column(String(200), nullable=False)
def to_dict(self):
return {
"id": self.id,
"profile_id": self.profile_id,
"contacttype": self.contacttype.to_dict(),
"content": self.content
}
class ContactType(db.Model): class ContactType(db.Model):
__tablename__ = "contacttype" __tablename__ = "contacttype"
@ -78,6 +96,9 @@ class ContactType(db.Model):
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
name = Column(String(25), nullable=False) name = Column(String(25), nullable=False)
def to_dict(self):
return {"id": self.id, "name": self.name}
class Address(db.Model): class Address(db.Model):
__tablename__ = "address" __tablename__ = "address"
@ -94,6 +115,19 @@ class Address(db.Model):
profile_id = Column(Integer, ForeignKey("profile.id"), nullable=False) profile_id = Column(Integer, ForeignKey("profile.id"), nullable=False)
profile = relationship("Profile", back_populates="address") profile = relationship("Profile", back_populates="address")
def to_dict(self):
return {
"id": self.id,
"name": self.name,
"street": self.street,
"house_number": self.house_number,
"additional": self.additional,
"postcode": self.postcode,
"city": self.city,
"country": self.country,
"profile_id": self.profile_id
}
class Skill(db.Model): class Skill(db.Model):
__tablename__ = "skill" __tablename__ = "skill"
@ -104,7 +138,11 @@ class Skill(db.Model):
profiles = relationship("ProfileSkill", back_populates="skill") profiles = relationship("ProfileSkill", back_populates="skill")
def to_dict(self): def to_dict(self):
return {"id": self.id, "name": self.name} return {
"id": self.id,
"name": self.name,
"icon_url": "/skills/{}/icon".format(self.id)
}
class ProfileSkill(db.Model): class ProfileSkill(db.Model):
@ -117,6 +155,13 @@ class ProfileSkill(db.Model):
profile = relationship("Profile", back_populates="skills") profile = relationship("Profile", back_populates="skills")
skill = relationship("Skill", back_populates="profiles") skill = relationship("Skill", back_populates="profiles")
def to_dict(self):
return {
"profile_id": self.profile_id,
"skill": self.skill.to_dict(),
"level": self.level
}
class Language(db.Model): class Language(db.Model):
__tablename__ = "language" __tablename__ = "language"
@ -127,7 +172,11 @@ class Language(db.Model):
profiles = relationship("ProfileLanguage", back_populates="language") profiles = relationship("ProfileLanguage", back_populates="language")
def to_dict(self): def to_dict(self):
return {"id": self.id, "name": self.name} return {
"id": self.id,
"name": self.name,
"icon_url": "/languages/{}/icon".format(self.id)
}
class ProfileLanguage(db.Model): class ProfileLanguage(db.Model):
@ -139,3 +188,10 @@ class ProfileLanguage(db.Model):
profile = relationship("Profile", back_populates="languages") profile = relationship("Profile", back_populates="languages")
language = relationship("Language", back_populates="profiles") language = relationship("Language", back_populates="profiles")
def to_dict(self):
return {
"profile_id": self.profile_id,
"language": self.language.to_dict(),
"level": self.level
}

View File

@ -1 +1 @@
from ki import models, commands, routes # noqa from ki import models, commands, routes # noqa

View File

@ -99,7 +99,7 @@ def login():
if token is None: if token is None:
return make_response({}, 403) return make_response({}, 403)
return make_response({"token": token.token}) return make_response({"token": token.token, "user_id": token.user_id})
@app.route("/users/<user_id>/profile") @app.route("/users/<user_id>/profile")
@ -115,7 +115,9 @@ def get_user_profile(user_id):
if profile is None: if profile is None:
return make_response({}, 404) return make_response({}, 404)
return make_response({"profile": profile.to_dict()}) return make_response({
"profile": profile.to_dict(),
})
@app.route("/users/<user_id>/profile", methods=["POST"]) @app.route("/users/<user_id>/profile", methods=["POST"])

View File

@ -0,0 +1,43 @@
from alembic import command
import json
import unittest
from app import app, db, migrate
from ki.actions import seed
class TestLoginEndpoint(unittest.TestCase):
def setUp(self):
app.debug = True
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///:memory:"
self.client = app.test_client()
with app.app_context():
config = migrate.get_config()
command.upgrade(config, "head")
seed(True)
def tearDown(self):
db.drop_all()
db.engine.dispose()
def test_login(self):
response1_data = self.login()
response2_data = self.login()
self.assertNotEqual(response1_data["token"], response2_data["token"])
def login(self):
response = self.client.post("/users/login",
data=json.dumps({
"username": "peter",
"password": "geheim"
}),
content_type="application/json")
self.assertEqual(response.status_code, 200)
self.assertIn("token", response.json)
return response.json
if __name__ == "main":
unittest.main()

View File

@ -3,11 +3,13 @@ import unittest
import json import json
from app import app, db, migrate from app import app, db, migrate
from ki.commands import seed from ki.actions import seed
from ki.models import Profile, User from ki.models import User
class TestProfileEndpoint(unittest.TestCase): class TestProfileEndpoint(unittest.TestCase):
maxDiff = None
def setUp(self): def setUp(self):
app.debug = True app.debug = True
app.config["TESTING"] = True app.config["TESTING"] = True
@ -18,16 +20,13 @@ class TestProfileEndpoint(unittest.TestCase):
config = migrate.get_config() config = migrate.get_config()
command.upgrade(config, "head") command.upgrade(config, "head")
seed() seed(True)
def tearDown(self): def tearDown(self):
db.drop_all()
db.engine.dispose() db.engine.dispose()
def test_create_profile(self): def test_update_profile(self):
user = User(auth_id="peter")
db.session.add(user)
db.session.commit()
login_data = {"username": "peter", "password": "geheim"} login_data = {"username": "peter", "password": "geheim"}
login_response = self.client.post("/users/login", login_response = self.client.post("/users/login",
data=json.dumps(login_data), data=json.dumps(login_data),
@ -37,7 +36,7 @@ class TestProfileEndpoint(unittest.TestCase):
self.assertIn("token", login_response.json) self.assertIn("token", login_response.json)
data = { data = {
"pronouns": "Herr Dr. Dr.", "pronouns": "Monsieur",
"volunteerwork": "ja", "volunteerwork": "ja",
"freetext": "Hallo", "freetext": "Hallo",
} }
@ -45,8 +44,7 @@ class TestProfileEndpoint(unittest.TestCase):
data=json.dumps(data), data=json.dumps(data),
content_type="application/json", content_type="application/json",
headers={ headers={
"Authorization": "Authorization": "Bearer " +
"Bearer " +
login_response.json["token"] login_response.json["token"]
}) })
@ -54,20 +52,11 @@ class TestProfileEndpoint(unittest.TestCase):
with app.app_context(): with app.app_context():
user = User.query.filter(User.id == 1).first() user = User.query.filter(User.id == 1).first()
profile = user.profile profile = user.profile
self.assertEqual("Herr Dr. Dr.", profile.pronouns) self.assertEqual("Monsieur", profile.pronouns)
self.assertEqual("ja", profile.volunteerwork) self.assertEqual("ja", profile.volunteerwork)
self.assertEqual("Hallo", profile.freetext) self.assertEqual("Hallo", profile.freetext)
def test_get_profile(self): def test_get_profile(self):
user = User(auth_id="peter")
db.session.add(user)
profile = Profile(user=user)
profile.nickname = "Popeter"
db.session.add(profile)
db.session.commit()
login_data = {"username": "peter", "password": "geheim"} login_data = {"username": "peter", "password": "geheim"}
login_response = self.client.post("/users/login", login_response = self.client.post("/users/login",
data=json.dumps(login_data), data=json.dumps(login_data),
@ -78,18 +67,65 @@ class TestProfileEndpoint(unittest.TestCase):
response = self.client.get("/users/1/profile", response = self.client.get("/users/1/profile",
headers={ headers={
"Authorization": "Authorization": "Bearer " +
"Bearer " + login_response.json["token"] login_response.json["token"]
}) })
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual( self.assertDictEqual(
response.json, { response.json, {
"profile": { "profile": {
"freetext": "", "user_id": 1,
"nickname": "Popeter", "nickname": "peternichtlustig",
"pronouns": "", "pronouns": "Herr Dr. Dr.",
"volunteerwork": "" "freetext": "Ich mag Kaffee",
"volunteerwork": "Gartenverein",
"address": {
"additional": "Hinterhaus",
"city": "Bielefeld",
"country": "Deutschland",
"house_number": "23i",
"id": 1,
"name": "Peter Nichtlustig",
"postcode": "13337",
"profile_id": 1,
"street": "Waldweg"
},
"contacts": [{
"id": 1,
"profile_id": 1,
"contacttype": {
"id": 1,
"name": "Matrix"
},
"content": "@peter:wtf-eg.de"
}],
"skills": [{
"profile_id": 1,
"skill": {
"id": 3,
"name": "Python",
"icon_url": "/skills/3/icon"
},
"level": 5
}],
"languages": [{
"profile_id": 1,
"language": {
"id": "de",
"name": "Deutsch",
"icon_url": "/languages/de/icon"
},
"level": 5
}, {
"profile_id": 1,
"language": {
"id": "fr",
"name": "Französisch",
"icon_url": "/languages/fr/icon"
},
"level": 3
}]
} }
}) })

View File

@ -1,8 +1,8 @@
from alembic import command from alembic import command
import unittest import unittest
from app import app, migrate from app import app, db, migrate
from ki.commands import seed from ki.actions import seed
class TestSkillsEndpoint(unittest.TestCase): class TestSkillsEndpoint(unittest.TestCase):
@ -15,20 +15,35 @@ class TestSkillsEndpoint(unittest.TestCase):
config = migrate.get_config() config = migrate.get_config()
command.upgrade(config, "head") command.upgrade(config, "head")
seed() seed(True)
def tearDown(self):
db.drop_all()
db.engine.dispose()
def test_skills_options(self):
response = self.client.options("/skills")
self.assertEqual(response.status_code, 200)
self.assertIn("Access-Control-Allow-Origin", response.headers)
self.assertEqual(response.headers["Access-Control-Allow-Origin"], "*")
def test_get_skills1(self): def test_get_skills1(self):
response = self.client.get("/skills?search=p") response = self.client.get("/skills?search=p")
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual( self.assertEqual(
{ {
"skills": [ "skills": [{
{"id": 1, "name": "PHP"}, "id": 1,
{"id": 3, "name": "Python"} "name": "PHP",
] "icon_url": "/skills/1/icon"
}, }, {
response.json "id": 3,
) "name": "Python",
"icon_url": "/skills/3/icon"
}]
}, response.json)
self.assertIn("Access-Control-Allow-Origin", response.headers)
self.assertEqual(response.headers["Access-Control-Allow-Origin"], "*")
if __name__ == "main": if __name__ == "main":