Compare commits
452 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 44e63b6105 | |||
| dae78ee976 | |||
| f813eaf9bf | |||
| 8baea20824 | |||
| 393ee997d9 | |||
| f67ce55d60 | |||
| 5e1390ab6b | |||
| 99348dc297 | |||
| 715b309c89 | |||
| 70676d5203 | |||
| 037e4ae6e8 | |||
| 8d6f13b61d | |||
| 03982ee620 | |||
| a8923b63d6 | |||
| 19975877cb | |||
| 2e0fad4bb2 | |||
| f0179c1840 | |||
| 37a9750d4d | |||
| 5575186570 | |||
| 9e179d5e06 | |||
| ea9a732803 | |||
| 0026b788b2 | |||
| da3a484f6c | |||
| a310d51f5e | |||
| 6732e30213 | |||
| 4e59eee200 | |||
| bb0b2a2628 | |||
| 32d636d3ee | |||
| c91b99cdb1 | |||
| ca73eba609 | |||
| fe46d57108 | |||
| 70e1835a4f | |||
| b7f17671d9 | |||
| 34659403a8 | |||
| 4fbf5fe0a4 | |||
| c6be0e6562 | |||
| 3623c7e9f4 | |||
| 298db5b78e | |||
| 1ca6de3090 | |||
| 07d0f2aa47 | |||
| 93e122b8a8 | |||
| 9678b663a0 | |||
| 992a2ec8e0 | |||
| b41bd41a54 | |||
| 30f59c36fb | |||
| f51d71e045 | |||
| 9c66238b82 | |||
| 2f0143a9ae | |||
| 45ff2c3757 | |||
| 321d45b628 | |||
| 2628d753f5 | |||
| a603e2dff8 | |||
| 96e05d908d | |||
| a5bf5668eb | |||
| 1ec5082faf | |||
| 6da5b97e19 | |||
| 7fd30e3eba | |||
| 3e9084ca1d | |||
| a28f82d31e | |||
| 04ecb8fc7e | |||
| 3e707e58a5 | |||
| 70d7f55996 | |||
| e408809e58 | |||
| 33fd4012f1 | |||
| 228938b695 | |||
| 72e91e3d62 | |||
| 3f6a719e9d | |||
| 490b99a848 | |||
| 0a899f5600 | |||
| 7825cc7293 | |||
| 8bb6e9e9a7 | |||
| 53c3acd39f | |||
| dd0254a08e | |||
| 9b53fb4bc0 | |||
| e17c3ad92c | |||
| 362d981532 | |||
| e12be3c2f6 | |||
| 3edae7f967 | |||
| 4d159b2793 | |||
| cb3b831f7a | |||
| acba330694 | |||
| eb22368055 | |||
| fac2f9b367 | |||
| 7319c32d73 | |||
| b3a484ce55 | |||
| 337b774074 | |||
| e70e598c57 | |||
| 5eef2f685c | |||
| d1953ef24c | |||
| ec4317d9e7 | |||
| cff84816fc | |||
| 7881a24eb1 | |||
| a0765ca18b | |||
| 913eb1cedf | |||
| 7035ebccca | |||
| 2031146fc7 | |||
| 35f30300b3 | |||
| 342f2862a5 | |||
| 3536ca5922 | |||
| 50ab7bc1f9 | |||
| 102d8f82d6 | |||
| f302c0a17d | |||
| a2b7086e9a | |||
| 704652c643 | |||
| 8741f6b113 | |||
| 9ffb79aa52 | |||
| a2551cc110 | |||
| 8d7bd7067e | |||
| db3e3ae291 | |||
| 1afd476c18 | |||
| 322cc9d6fb | |||
| 5f9a7a6994 | |||
| 3809ba9726 | |||
| 79cc0bcd55 | |||
| 7cb03d40d4 | |||
| 34faf53347 | |||
| 8d2a26a9d1 | |||
| a05edb73d3 | |||
| 2c69e1b945 | |||
| 9cae6ea890 | |||
| 49a3b1449e | |||
| 02634c8e02 | |||
| 11efcdd579 | |||
| a0f062c697 | |||
| 1dc5d45e96 | |||
| 7382c4e4bf | |||
| 867996ae27 | |||
| 798c860091 | |||
| 86a65e4680 | |||
| 4a530826a8 | |||
| 6f316401f7 | |||
| 8a8820be2f | |||
| 51278fd8bc | |||
| 2d86d548a3 | |||
| 6380d39afb | |||
| 9420b1fd05 | |||
| fcd23c07ab | |||
| c92193057c | |||
| a845be5394 | |||
| c4a0f5a0ea | |||
| 14735d8898 | |||
| d8250c691c | |||
| 87d14240cf | |||
| 50d4006b96 | |||
| d9b006904a | |||
| 3aa225f3cd | |||
| a4e1aa5032 | |||
| 912ce44513 | |||
| 6d093af511 | |||
| be7a5fe2e2 | |||
| 40f6558a6e | |||
| 808c27e104 | |||
| b8342ba66a | |||
| 9d05cd8290 | |||
| 4860c0a0d9 | |||
| 9d4c041153 | |||
| ce72ff7801 | |||
| fb3328cb23 | |||
| dac0fa1953 | |||
| 63672328f0 | |||
| a927ab8a13 | |||
| c6374d88b6 | |||
| f72017df01 | |||
| 26635bbbcc | |||
| 86973562ff | |||
| 0c1bdba358 | |||
| 8481e963a3 | |||
| 39ba51f11a | |||
| d889df951c | |||
| 19f8ff006c | |||
| ed77526f5c | |||
| 6f6dc09f01 | |||
| 5049ef415d | |||
| 441227bc15 | |||
| 78575075e5 | |||
| 4029031bf0 | |||
| f1ea08bc30 | |||
| e68f04117d | |||
| d07914bdb5 | |||
| 38eb4cd9cd | |||
| ab104d2acd | |||
| 0376acdc9e | |||
| abbf23456b | |||
| 6a156dd9db | |||
| 9fb9bd447a | |||
| ba69f54465 | |||
| 9a450ea44a | |||
| 242a45a92e | |||
| ad472ca8ff | |||
| 928a98904b | |||
| 2882c1a4c8 | |||
| 1e0c050f99 | |||
| 744874a54c | |||
| 4dfa033f0b | |||
| d56ab02c27 | |||
| 01c5a0ed53 | |||
| 06fcfcf3a1 | |||
| 6567cd87d5 | |||
| 9efe0d7489 | |||
| d7b6ef3c72 | |||
| 0a31492733 | |||
| c96ff2f112 | |||
| 2b0d1c6ca7 | |||
| 90342011f5 | |||
| 27f9c79cf3 | |||
| de1ba7c9f2 | |||
| c2fb77816a | |||
| 4be0b75a1c | |||
| 09aa42cfb8 | |||
| c5008e3656 | |||
| e5224fe1e2 | |||
| b834567309 | |||
| b1921d4926 | |||
| 00c6920c8a | |||
| 1fe323cbdd | |||
| 3a58ff0ef1 | |||
| d06c24bde0 | |||
| 2359743b20 | |||
| 437936ad64 | |||
| 27dd56b6fb | |||
| ff5a92897c | |||
| e9c38d56d8 | |||
| 57bc12b621 | |||
| 1ff188eddd | |||
| b443b39457 | |||
| 22615da3b4 | |||
| d09cbf3b2c | |||
| 66fcfab4da | |||
| a7d0f49190 | |||
| 519571f84c | |||
| 30847e8bc2 | |||
| df69a64ffc | |||
| 157f1c487c | |||
| 1e5a691d73 | |||
| 43fe25ba64 | |||
| 851269b13f | |||
| 0e5e18b035 | |||
| f543e83e80 | |||
| 0b72c20e32 | |||
| 04e6eb3ec2 | |||
| 4e1efff7f2 | |||
| d45f7195f5 | |||
| dc6347a6eb | |||
| 1304cffa2a | |||
| 656b2d1d95 | |||
| 9b9406ab4b | |||
| 3630b360d7 | |||
| c6a3445774 | |||
| 9b7e9a6d92 | |||
| 3de975203e | |||
| 38669947cd | |||
| e03b6e6606 | |||
| 8491842a04 | |||
| eb80250335 | |||
| dec348b032 | |||
| 5071bd6172 | |||
| f98971270a | |||
| e2d55ffce2 | |||
| 37cd2eda4c | |||
| aa0ae01227 | |||
| 54ded7d3f9 | |||
| ef38d8be46 | |||
| 673bbc09eb | |||
| 9fa62e9a2f | |||
| 32e0704f53 | |||
| 39ab86fe3e | |||
| f9e7c4a904 | |||
| 0adca6bd2e | |||
| 5c4e4c7f5a | |||
| 76e1c71cd1 | |||
| 67b31060b2 | |||
| 110d426efd | |||
| 62bb317c16 | |||
| a379390ca1 | |||
| 75ece86fb0 | |||
| 26af46fa12 | |||
| 027b9c370c | |||
| 59c9fec51c | |||
| 98ea2d8a47 | |||
| 0b9aed128a | |||
| 4ebba7f74a | |||
| 11c36f2111 | |||
| ef78ef9dcd | |||
| 34984d4a5e | |||
| 319324503f | |||
| 683f50b199 | |||
| 2d6c474baa | |||
| b302681bbf | |||
| 90d8ee2ea9 | |||
| f505fae3e6 | |||
| 2433c7828d | |||
| 0edde936cc | |||
| 2e11621e5b | |||
| 5f706d9236 | |||
| d3350c06e0 | |||
| 648feaa762 | |||
| 6d4662a1cb | |||
| 523adcd256 | |||
| 5f0ab2ddbf | |||
| 6e3b3bd52f | |||
| 6dcbc1c652 | |||
| 80e00e2b05 | |||
| 80298d174f | |||
| b790906b23 | |||
| e7164bb613 | |||
| a2a2b9830e | |||
| eb0bfdbd21 | |||
| d9116e51ca | |||
| 3188a8e520 | |||
| a9c1d06ece | |||
| c32292c7d9 | |||
| f2ffcaa5a9 | |||
| d0216bb85c | |||
| 62263848f1 | |||
| 242199399b | |||
| 66d9ee0a75 | |||
| c2222d4426 | |||
| 368b28d241 | |||
| c5df450a96 | |||
| c1e0a32a08 | |||
| 373db50995 | |||
| f422e31a42 | |||
| f7cf54cab2 | |||
| d17e6710e6 | |||
| f64cec7d2e | |||
| ff87767aab | |||
| 08ec9bb9cc | |||
| eb63c9fc1c | |||
| eec157d7f3 | |||
| 1ce3c28e94 | |||
| 52a646fe04 | |||
| 8c4f6e886c | |||
| a9d6bf50d6 | |||
| 8f0c9feecf | |||
| 5fba7d6249 | |||
| 3d755bca8c | |||
| ff04e645e4 | |||
| 2c72e01353 | |||
| 3836e00174 | |||
| b33f29d620 | |||
| 8afeec21cc | |||
| 1778a0509c | |||
| 86ae2197f3 | |||
| 4859daf0b3 | |||
| ff9cad1f47 | |||
| 01161db33b | |||
| a7728e50dd | |||
| edb6d73c06 | |||
| 0f50daf289 | |||
| f4b55b7863 | |||
| 00de36ca44 | |||
| 59f47dec26 | |||
| 8c4b997f0f | |||
| b2e2ff6699 | |||
| ff98f700f9 | |||
| fe48a2a336 | |||
| 80c8d9bc90 | |||
| 31ebace4cd | |||
| b3a3a9815f | |||
| 81a16680e4 | |||
| 04e91a0e8d | |||
| 72b2a18c63 | |||
| 029089b32d | |||
| 3ea513785a | |||
| 8dc576983c | |||
| a7ada33ff9 | |||
| 59a59685d1 | |||
| 2f0024630d | |||
| e9706d0e4f | |||
| baa329ad5d | |||
| 389408a59e | |||
| 2f4009d002 | |||
| dab6f98ec2 | |||
| b23e439eb5 | |||
| fdea5858ba | |||
| 96fc8a5569 | |||
| c408a6532e | |||
| 09e973fab1 | |||
| 971247e67c | |||
| 59dcb382ce | |||
| 86af724ccc | |||
| e5cc62bbbf | |||
| 4b63de6fc4 | |||
| d0d63a6e29 | |||
| d7f57c5b3a | |||
| 777cca4917 | |||
| e05fcb423c | |||
| 7457b8a24d | |||
| 8a3fdc40eb | |||
| 192be6e28b | |||
| 6ba91f1fab | |||
| 4725432c32 | |||
| 4e5b6c565a | |||
| 4e216891e6 | |||
| 562738f945 | |||
| 7ffddf41f8 | |||
| 88f37928ac | |||
| fcd7ae7c04 | |||
| 995d42640f | |||
| 13583bd6ab | |||
| 522c804369 | |||
| 046ae89d29 | |||
| 9ffec51537 | |||
| c07ec5855a | |||
| c445a4a66c | |||
| 96dd77e455 | |||
| 84f1f9239b | |||
| a24a7f02b9 | |||
| d3c50dc608 | |||
| 34a4f027f5 | |||
| 66ed4c0713 | |||
| 1038e9c67e | |||
| 6a3398e8de | |||
| 8a87288d25 | |||
| 1d4a509c7e | |||
| 29d2f6b90d | |||
| 5d6cf1fb80 | |||
| 95e72b7ab2 | |||
| d17d1fb1b7 | |||
| d0d822b8f4 | |||
| f6abe6f999 | |||
| 8a5bbaa229 | |||
| 5750d62fe0 | |||
| 71e683fbe3 | |||
| e214144ec3 | |||
| 0faf19e319 | |||
| 10552c5f2a | |||
| 4181b246a1 | |||
| c39979e253 | |||
| 807c599b85 | |||
| 9e186c32ba | |||
| e3d0709b31 | |||
| bd8fc6b404 | |||
| 8500ed8fbd | |||
| cb86c9a2ae | |||
| bb0f4244c7 | |||
| cd2fe4e8f2 | |||
| bf3f1f783c | |||
| 37f509ff76 | |||
| b58dbde83c | |||
| 0da0a02bac | |||
| fef74dd995 | |||
| 2356365197 | |||
| 5a29c8c49d | |||
| e365c394d8 | |||
| 22371b8f15 | |||
| 32feb46f88 | |||
| 6c77fe4da6 | |||
| 5dc7442d03 | |||
| 9f664737b6 | |||
| 1c3b4367a2 | |||
| 9a9757c914 |
@@ -1,5 +1,11 @@
|
||||
[report]
|
||||
show_missing = True
|
||||
omit =
|
||||
*/migrations/*
|
||||
*/admin.py
|
||||
*/translation.py
|
||||
[run]
|
||||
omit =
|
||||
*/migrations/*
|
||||
*/admin.py
|
||||
*/translation.py
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "Deploying to development."
|
||||
set -e
|
||||
set -x
|
||||
|
||||
pushd deployment
|
||||
|
||||
docker-compose down
|
||||
docker pull "$1"
|
||||
docker-compose up -d
|
||||
|
||||
popd
|
||||
|
||||
set +x
|
||||
set +e
|
||||
@@ -0,0 +1,32 @@
|
||||
.DS_Store
|
||||
.dockerignore
|
||||
.git/
|
||||
.husky/
|
||||
.venv/
|
||||
.vscode/
|
||||
collected_static/
|
||||
logs/
|
||||
!logs/README
|
||||
media/
|
||||
!media/REMOVE_ME
|
||||
misc/
|
||||
node_modules/
|
||||
scripts/
|
||||
.coverage
|
||||
.coveragerc
|
||||
.env*
|
||||
.eslintignore
|
||||
.eslintrc.json
|
||||
.gitignore
|
||||
.gitlab-ci.yml
|
||||
.python-version
|
||||
docker-compose.yml
|
||||
!manage.py
|
||||
package*.json
|
||||
!poetry.lock
|
||||
!production_entrypoint.sh
|
||||
pycodestyle.cfg
|
||||
!pyproject.toml
|
||||
pyright.json
|
||||
README.md
|
||||
stack-compose*.yml
|
||||
@@ -0,0 +1,11 @@
|
||||
DEPLOY_ENV=local
|
||||
SENTRY_DSN=
|
||||
HOST=api.dev.sahkoinsinoorikilta.fi
|
||||
DEBUG=True
|
||||
SECRET_KEY=7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp(
|
||||
DB_NAME=postgres
|
||||
DB_USER=postgres
|
||||
DB_PASSWD=postgres
|
||||
DB_HOST=db
|
||||
DB_PORT=5432
|
||||
EMAIL_API_KEY=
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
DEPLOY_ENV=local
|
||||
#SENTRY_DSN=
|
||||
HOST=localhost
|
||||
DEBUG=True
|
||||
SECRET_KEY=7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp(
|
||||
DB_NAME=postgres
|
||||
DB_USER=postgres
|
||||
DB_PASSWD=postgres
|
||||
DB_HOST=db
|
||||
DB_PORT=5432
|
||||
EMAIL_API_KEY=
|
||||
GROUP_KEY=
|
||||
GOOGLE_CREDS='{}'
|
||||
+4
-5
@@ -4,11 +4,10 @@
|
||||
"jquery": true
|
||||
},
|
||||
"globals": {
|
||||
"angular": 1,
|
||||
"noty": 1,
|
||||
"app": 1,
|
||||
"_": 1,
|
||||
"moment": 1
|
||||
"angular": true,
|
||||
"noty": true,
|
||||
"_": true,
|
||||
"moment": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"rules": {
|
||||
|
||||
+12
-21
@@ -1,23 +1,14 @@
|
||||
*.swp
|
||||
sikweb/settings.py
|
||||
*~
|
||||
*.pyc
|
||||
*.sqlite3
|
||||
uwsgi.ini
|
||||
uwsgi.log
|
||||
members/logs/*
|
||||
.idea/
|
||||
logs/
|
||||
/media/
|
||||
node_modules/
|
||||
/.coverage
|
||||
db.sqlite3
|
||||
requirements_henu.txt
|
||||
/collected_static/
|
||||
mydatabase
|
||||
settings.json
|
||||
.vscode/
|
||||
.DS_Store
|
||||
.env
|
||||
*.pyc
|
||||
/collected_static/
|
||||
/media/
|
||||
logs/
|
||||
members/logs/*
|
||||
node_modules/
|
||||
.coverage
|
||||
.vscode/
|
||||
.idea/
|
||||
*.code-workspace
|
||||
sik_test
|
||||
venv/
|
||||
venv/
|
||||
.venv/
|
||||
+83
-54
@@ -1,102 +1,131 @@
|
||||
stages:
|
||||
- test
|
||||
- setup
|
||||
- audit
|
||||
- lint
|
||||
- test
|
||||
- publish
|
||||
- deploy
|
||||
|
||||
install:
|
||||
image: node:16
|
||||
stage: setup
|
||||
script:
|
||||
- npm ci
|
||||
artifacts:
|
||||
paths:
|
||||
- node_modules
|
||||
expire_in: 1 week
|
||||
|
||||
audit:
|
||||
image: python:3.9
|
||||
stage: audit
|
||||
needs: []
|
||||
before_script:
|
||||
- pip install poetry==1.1.13
|
||||
- poetry config virtualenvs.create false
|
||||
- poetry install --no-interaction --no-ansi
|
||||
script:
|
||||
- safety check
|
||||
|
||||
test:
|
||||
image: python:3.5
|
||||
image: python:3.9
|
||||
stage: test
|
||||
needs: []
|
||||
services:
|
||||
- postgres:latest
|
||||
- postgres:12
|
||||
variables:
|
||||
POSTGRES_DB: ci
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
|
||||
DB_HOST: postgres
|
||||
before_script:
|
||||
- pip install poetry==1.1.13
|
||||
- poetry config virtualenvs.create false
|
||||
- poetry install --no-interaction --no-ansi
|
||||
script:
|
||||
- python -V
|
||||
- pip install -r requirements.txt
|
||||
- cp sikweb/settings-sample.py sikweb/default_settings.py
|
||||
- cp sikweb/.ci-settings.py sikweb/settings.py
|
||||
- python manage.py migrate --noinput
|
||||
- python manage.py createdefaultadmin
|
||||
- python manage.py test
|
||||
|
||||
pycodestyle:
|
||||
image: python:3.5
|
||||
lint:py:
|
||||
image: python:3.9
|
||||
stage: lint
|
||||
needs: []
|
||||
script:
|
||||
- pip install pycodestyle
|
||||
- pycodestyle --config=setup.cfg --count .
|
||||
- pip install black==22.3.0
|
||||
- black --check .
|
||||
|
||||
eslint:
|
||||
image: node:alpine
|
||||
lint:js:
|
||||
image: node:16
|
||||
stage: lint
|
||||
before_script:
|
||||
- npm install
|
||||
needs: ["install"]
|
||||
script:
|
||||
- npm run eslint
|
||||
- npm run lint:js
|
||||
|
||||
remark:
|
||||
image: node:alpine
|
||||
lint:md:
|
||||
image: node:16
|
||||
stage: lint
|
||||
before_script:
|
||||
- npm install
|
||||
needs: ["install"]
|
||||
script:
|
||||
- npm run remark
|
||||
- npm run lint:md
|
||||
|
||||
publish:
|
||||
stage: publish
|
||||
image: docker:stable
|
||||
needs: ["test", "lint:py", "lint:js", "lint:md"]
|
||||
services:
|
||||
- docker:stable-dind
|
||||
only:
|
||||
- develop
|
||||
- master
|
||||
script:
|
||||
- docker info
|
||||
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
|
||||
- docker build . -t "$IMAGE_NAME"
|
||||
- docker push "$IMAGE_NAME"
|
||||
|
||||
deploy_dev:
|
||||
deploy:dev:
|
||||
stage: deploy
|
||||
image: alpine:latest
|
||||
environment:
|
||||
name: dev
|
||||
url: http://web.sik.party:8000
|
||||
image: docker:stable
|
||||
only:
|
||||
- develop
|
||||
before_script:
|
||||
- pwd
|
||||
- apk add --update openssh
|
||||
- ssh -V
|
||||
- mkdir -p ~/.ssh
|
||||
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
|
||||
- chmod 600 ~/.ssh/id_rsa
|
||||
- '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
|
||||
script:
|
||||
- scp docker-compose.yml $DEV_SSH_USER@$DEV_SSH_HOST:~/deployment/docker-compose.yml
|
||||
- scp .deploy_dev.sh $DEV_SSH_USER@$DEV_SSH_HOST:~/deployment/deploy_dev.sh
|
||||
- ssh $DEV_SSH_USER@$DEV_SSH_HOST "docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY"
|
||||
- ssh $DEV_SSH_USER@$DEV_SSH_HOST "bash ~/deployment/deploy_dev.sh \"$IMAGE_NAME\""
|
||||
|
||||
deploy_production:
|
||||
stage: deploy
|
||||
image: alpine:latest
|
||||
environment:
|
||||
name: production
|
||||
url: https://sika.sahkoinsinoorikilta.fi
|
||||
when: manual
|
||||
name: dev
|
||||
url: http://api.dev.sahkoinsinoorikilta.fi
|
||||
variables:
|
||||
DOCKER_HOST: $DEV_CI_DOCKER_HOST
|
||||
DOCKER_TLS_VERIFY: 1
|
||||
before_script:
|
||||
- mkdir -p ~/.docker
|
||||
- echo "$DEV_TLSCACERT" > ~/.docker/ca.pem
|
||||
- echo "$DEV_TLSCERT" > ~/.docker/cert.pem
|
||||
- echo "$DEV_TLSKEY" > ~/.docker/key.pem
|
||||
- docker login -u gitlab-ci-token -p "$CI_BUILD_TOKEN" "$CI_REGISTRY"
|
||||
script:
|
||||
- docker stack deploy --with-registry-auth -c stack-compose-dev.yml "$SERVICE_NAME"
|
||||
after_script:
|
||||
- docker logout "$CI_REGISTRY"
|
||||
|
||||
deploy:production:
|
||||
stage: deploy
|
||||
image: docker:stable
|
||||
only:
|
||||
- master
|
||||
environment:
|
||||
name: production
|
||||
url: https://api.sahkoinsinoorikilta.fi
|
||||
when: manual
|
||||
variables:
|
||||
DOCKER_HOST: $CI_DOCKER_HOST
|
||||
DOCKER_TLS_VERIFY: 1
|
||||
before_script:
|
||||
- pwd
|
||||
- apk add --update openssh
|
||||
- ssh -V
|
||||
- mkdir -p ~/.ssh
|
||||
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
|
||||
- chmod 600 ~/.ssh/id_rsa
|
||||
- echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
|
||||
- mkdir -p ~/.docker
|
||||
- echo "$TLSCACERT" > ~/.docker/ca.pem
|
||||
- echo "$TLSCERT" > ~/.docker/cert.pem
|
||||
- echo "$TLSKEY" > ~/.docker/key.pem
|
||||
- docker login -u gitlab-ci-token -p "$CI_BUILD_TOKEN" "$CI_REGISTRY"
|
||||
script:
|
||||
- ssh $PROD_SSH_USER@$PROD_SSH_HOST "zsh ~/deploy.sh"
|
||||
- docker stack deploy --with-registry-auth -c stack-compose.yml "$SERVICE_NAME"
|
||||
after_script:
|
||||
- docker logout "$CI_REGISTRY"
|
||||
@@ -0,0 +1 @@
|
||||
_
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
PURPLE='\033[0;35m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
. "${VIRTUAL_ENV}/bin/activate"
|
||||
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
printf "${PURPLE}Failed to find virtualenv. Skipping pre-commit hook.\n${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
npm run lint
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
PURPLE='\033[0;35m'
|
||||
NC='\033[0m' # No Color
|
||||
@@ -14,10 +15,8 @@ fi
|
||||
set -e
|
||||
|
||||
printf "${PURPLE}Running pre-push tests.${NC}\n"
|
||||
printf "${PURPLE}npm tests...${NC}\n"
|
||||
npm test
|
||||
printf "${PURPLE}pycodestyle tests...${NC}\n"
|
||||
pycodestyle .
|
||||
printf "${PURPLE}linters...${NC}\n"
|
||||
npm run lint
|
||||
printf "${PURPLE}unit tests...${NC}\n"
|
||||
python -Wall manage.py test --noinput
|
||||
set +e
|
||||
+1
-1
@@ -1 +1 @@
|
||||
3.6.8
|
||||
3.9
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
global_static
|
||||
+27
-7
@@ -1,8 +1,28 @@
|
||||
FROM python:3.5
|
||||
FROM python:3.9-slim-buster as builder
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
ENV IS_DOCKER 1
|
||||
RUN mkdir /code
|
||||
WORKDIR /code
|
||||
ADD requirements.txt /code/
|
||||
RUN env
|
||||
ADD . /code/
|
||||
COPY . ./
|
||||
|
||||
ENV POETRY_VERSION=1.1.13
|
||||
|
||||
RUN pip install "poetry==$POETRY_VERSION"
|
||||
RUN poetry export --without-hashes > requirements.txt
|
||||
|
||||
FROM python:3.9-slim-buster as server
|
||||
|
||||
WORKDIR /app
|
||||
COPY . ./
|
||||
COPY --from=builder requirements.txt ./
|
||||
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
# prevents python creating .pyc files
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
# pip
|
||||
PIP_NO_CACHE_DIR=off \
|
||||
PIP_DISABLE_PIP_VERSION_CHECK=on \
|
||||
PIP_DEFAULT_TIMEOUT=100
|
||||
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y build-essential
|
||||
RUN pip install --no-deps -r requirements.txt
|
||||
|
||||
RUN python manage.py collectstatic --noinput
|
||||
CMD ["sh", "-c", "./production_entrypoint.sh"]
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
[[source]]
|
||||
name = "pypi"
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
|
||||
[dev-packages]
|
||||
flake8 = "*"
|
||||
autopep8 = "*"
|
||||
|
||||
[packages]
|
||||
decorator = "==4.0.9"
|
||||
ipython = "==4.2.0"
|
||||
pexpect = "==4.1.0"
|
||||
pickleshare = "==0.7.2"
|
||||
ptyprocess = "==0.5.1"
|
||||
pytz = "==2016.4"
|
||||
simplegeneric = "==0.8.1"
|
||||
traitlets = "==4.2.1"
|
||||
requests = "==2.11.1"
|
||||
django-nocaptcha-recaptcha = "==0.0.19"
|
||||
django-cors-headers = "==2.0.1"
|
||||
djangorestframework = "==3.8.2"
|
||||
djangorestframework-jwt = "==1.11.0"
|
||||
coverage = "==4.3.4"
|
||||
django-nose = "==1.4.5"
|
||||
nose-exclude = "==0.5.0"
|
||||
psycopg2-binary = "==2.7.6.1"
|
||||
django-bootstrap3 = "==8.2.3"
|
||||
django-tables2 = "==1.6.1"
|
||||
pycodestyle = "==2.3.1"
|
||||
dealer = "==2.0.5"
|
||||
django-modeltranslation = "==0.13b1"
|
||||
django-auditlog = "==0.4.5"
|
||||
django-phonenumber-field = "==1.3.0"
|
||||
django-autocomplete-light = "==3.2.10"
|
||||
six = "==1.10.0"
|
||||
django-suit = "==0.2.26"
|
||||
telepot = "==12.3"
|
||||
pyexcel = "==0.5.10"
|
||||
pyexcel-xlsx = "==0.5.5"
|
||||
django-import-export = "==0.7.0"
|
||||
openpyxl = "==2.4.11"
|
||||
django-app-namespace-template-loader = "==0.4.1"
|
||||
django-filter = "==2.0.0"
|
||||
"backports.shutil_get_terminal_size" = "==1.0.0"
|
||||
Django = "==2.1.5"
|
||||
ipython_genutils = "==0.1.0"
|
||||
Pillow = "==5.4.1"
|
||||
PyJWT = "==1.6.4"
|
||||
|
||||
[requires]
|
||||
python_version = "3.6"
|
||||
Generated
-706
@@ -1,706 +0,0 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "fd7aa96e8c4fc9e3fa88b6f3b28a1101d7efd8b58e964fbece854850acf8bcea"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.6"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"aiohttp": {
|
||||
"hashes": [
|
||||
"sha256:00d198585474299c9c3b4f1d5de1a576cc230d562abc5e4a0e81d71a20a6ca55",
|
||||
"sha256:0155af66de8c21b8dba4992aaeeabf55503caefae00067a3b1139f86d0ec50ed",
|
||||
"sha256:09654a9eca62d1bd6d64aa44db2498f60a5c1e0ac4750953fdd79d5c88955e10",
|
||||
"sha256:199f1d106e2b44b6dacdf6f9245493c7d716b01d0b7fbe1959318ba4dc64d1f5",
|
||||
"sha256:296f30dedc9f4b9e7a301e5cc963012264112d78a1d3094cd83ef148fdf33ca1",
|
||||
"sha256:368ed312550bd663ce84dc4b032a962fcb3c7cae099dbbd48663afc305e3b939",
|
||||
"sha256:40d7ea570b88db017c51392349cf99b7aefaaddd19d2c78368aeb0bddde9d390",
|
||||
"sha256:629102a193162e37102c50713e2e31dc9a2fe7ac5e481da83e5bb3c0cee700aa",
|
||||
"sha256:6d5ec9b8948c3d957e75ea14d41e9330e1ac3fed24ec53766c780f82805140dc",
|
||||
"sha256:87331d1d6810214085a50749160196391a712a13336cd02ce1c3ea3d05bcf8d5",
|
||||
"sha256:9a02a04bbe581c8605ac423ba3a74999ec9d8bce7ae37977a3d38680f5780b6d",
|
||||
"sha256:9c4c83f4fa1938377da32bc2d59379025ceeee8e24b89f72fcbccd8ca22dc9bf",
|
||||
"sha256:9cddaff94c0135ee627213ac6ca6d05724bfe6e7a356e5e09ec57bd3249510f6",
|
||||
"sha256:a25237abf327530d9561ef751eef9511ab56fd9431023ca6f4803f1994104d72",
|
||||
"sha256:a5cbd7157b0e383738b8e29d6e556fde8726823dae0e348952a61742b21aeb12",
|
||||
"sha256:a97a516e02b726e089cffcde2eea0d3258450389bbac48cbe89e0f0b6e7b0366",
|
||||
"sha256:acc89b29b5f4e2332d65cd1b7d10c609a75b88ef8925d487a611ca788432dfa4",
|
||||
"sha256:b05bd85cc99b06740aad3629c2585bda7b83bd86e080b44ba47faf905fdf1300",
|
||||
"sha256:c2bec436a2b5dafe5eaeb297c03711074d46b6eb236d002c13c42f25c4a8ce9d",
|
||||
"sha256:cc619d974c8c11fe84527e4b5e1c07238799a8c29ea1c1285149170524ba9303",
|
||||
"sha256:d4392defd4648badaa42b3e101080ae3313e8f4787cb517efd3f5b8157eaefd6",
|
||||
"sha256:e1c3c582ee11af7f63a34a46f0448fca58e59889396ffdae1f482085061a2889"
|
||||
],
|
||||
"version": "==3.5.4"
|
||||
},
|
||||
"async-timeout": {
|
||||
"hashes": [
|
||||
"sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f",
|
||||
"sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"
|
||||
],
|
||||
"version": "==3.0.1"
|
||||
},
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
"sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79",
|
||||
"sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"
|
||||
],
|
||||
"version": "==19.1.0"
|
||||
},
|
||||
"babel": {
|
||||
"hashes": [
|
||||
"sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669",
|
||||
"sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23"
|
||||
],
|
||||
"version": "==2.6.0"
|
||||
},
|
||||
"backports.csv": {
|
||||
"hashes": [
|
||||
"sha256:1277dfff73130b2e106bf3dd347adb3c5f6c4340882289d88f31240da92cbd6d",
|
||||
"sha256:21f6e09bab589e6c1f877edbc40277b65e626262a86e69a70137db714eaac5ce"
|
||||
],
|
||||
"version": "==1.0.7"
|
||||
},
|
||||
"backports.shutil-get-terminal-size": {
|
||||
"hashes": [
|
||||
"sha256:0975ba55054c15e346944b38956a4c9cbee9009391e41b86c68990effb8c1f64",
|
||||
"sha256:713e7a8228ae80341c70586d1cc0a8caa5207346927e23d09dcbcaf18eadec80"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.0.0"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||
],
|
||||
"version": "==3.0.4"
|
||||
},
|
||||
"coverage": {
|
||||
"hashes": [
|
||||
"sha256:01406019418aabb2d4741647cc79b0e0deb0b8c5a6f936936c303e2f82ec8e5b",
|
||||
"sha256:01a07b2b9212d4da3a1294436b58ac53f1d7aa445bda666648a5357048dc7ef3",
|
||||
"sha256:024682371464c6e3caa975aba12b4d5428f35613489340fce1334c74d590a057",
|
||||
"sha256:07c15c4a2287116a41d5966f1f5a7be765640c2e5a1917f882850a24615db6d3",
|
||||
"sha256:1d23dea598fb4d61a8577d0eb0cb2b7932db0c8d2e1394088ad5f64e3fe1febf",
|
||||
"sha256:1eeb9de833c3b976ee118a8d838af437bfa596bf60a5bf0705f4370e6d181a52",
|
||||
"sha256:229ab9c0d53c55d698b8784d53077bef7a5f1fb5d27e90dc7b6f91243b024513",
|
||||
"sha256:2f5a8bf29bdc69976d0913745daab11f8265e46ec41153f5e1e1794088019dad",
|
||||
"sha256:2f959bc1b40a3ef2c5f0c7bc282226d6d4bd585b239bcce321013afc18ff0a0f",
|
||||
"sha256:36407249a0b6669c6ad4425b0f29685579df745480c03afa70f101f09f4eead3",
|
||||
"sha256:3efa49e3da8f32071ee3d5d464cc6b6f8818524d4099b4a94b86a70b8c88d4f5",
|
||||
"sha256:422bcc6270e1c0cd9043048ce244f49072e9bd78a2c028c2ad2cfd58c79f5936",
|
||||
"sha256:4fa2b181c3bf94cfdf841148d5d9abcab1890188dd908a639bcf7a38c50092bc",
|
||||
"sha256:57c0c217270e628380f4befbbf8c5312b88ba7d81fd3d1b2218a25a2608f603c",
|
||||
"sha256:6ae76a6cd594107ad45525278e8addeae4628a59c8cde3999548d7fe1646465b",
|
||||
"sha256:6d3c762c87062a29771015f942752caef42fcc7fe4be2b03186f96788242290c",
|
||||
"sha256:8a82664931a071399d703d65af2521e2202b34f2d8db20fa22a922fec0339022",
|
||||
"sha256:8b282292973a1dc4eccfcc0776e0fde75b5b3de2e35164c2d854f7dd80149e4b",
|
||||
"sha256:9a7874ca91cee8714277cd6d1b52374809ab925bf6ae92607bf02509019caadb",
|
||||
"sha256:9c3e6551597593c1afedcbccf1371995f94457aea82cac726d1f3a25f4507386",
|
||||
"sha256:a791068e1bbe443fcd3179b1c180c27a7fc58c1554b0d10311b7659d2d2d76f5",
|
||||
"sha256:adf04843188418b012dd1974e397a7ac3faa1855cbcd69083e3af4da6de9dd81",
|
||||
"sha256:adfbbd4a1d22fd77b13ff992946b19873407e035504abe9ba537494fe013300f",
|
||||
"sha256:b25aa3531220faaf1727fc29bc000798476b4a30f429dc07898d5da48caefa15",
|
||||
"sha256:c12f34c0b50e9e8bf8c049b6c8ca59929c33cea4b1c48362c99c36838c1ee025",
|
||||
"sha256:c736faa1688222a6c8a5d8be4b66ec373ad6dab27fced8ca0d2c80fed70ac6e3",
|
||||
"sha256:ca36d83cd591d027952e5019149c4386e7058cd674bf8cb52dc622f768d689e9",
|
||||
"sha256:e1fb21a807aa0b5cc79806d8ef09078acaa83f994e15f0f7277489ca8eda51b7",
|
||||
"sha256:e53199ae110cb7e250dd5505fde34452514f4eb2f1fb7532270d2ea037454b09",
|
||||
"sha256:ea9808001dcf34d368cbef430e7885fdc76a2cf8ea96a8ed8b653797dd9555bb",
|
||||
"sha256:eaaefe0f6aa33de5a65f48dd0040d7fe08cac9ac6c35a56d0a7db109c3e733df",
|
||||
"sha256:f27772c9ee88ed3f2a784181f3d1724561499e7e448ed1706153336baa706bd5",
|
||||
"sha256:f99066d76274800145a2e658026b30962eb5079346249197e88b55c9a7855e6a",
|
||||
"sha256:fd3373ccd561b79932d12a986674e642816cfc4db4507b6a22ab30c318a85429"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.3.4"
|
||||
},
|
||||
"dealer": {
|
||||
"hashes": [
|
||||
"sha256:0a5a536fdecd9c7679534a19c59392cd21989037c14782328970a185b39e7560",
|
||||
"sha256:baaac37a4c7928545cb8b0335f48abd0ea51a0274159a9a989afb8b71f8b11c3"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.0.5"
|
||||
},
|
||||
"decorator": {
|
||||
"hashes": [
|
||||
"sha256:90022e83316363788a55352fe39cfbed357aa3a71d90e5f2803a35471de4bba8",
|
||||
"sha256:f4718552326c99544a6ec602d96b7d03ef61180cf4a492c515ecb2438dd14ccc"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.0.9"
|
||||
},
|
||||
"defusedxml": {
|
||||
"hashes": [
|
||||
"sha256:24d7f2f94f7f3cb6061acb215685e5125fbcdc40a857eff9de22518820b0a4f4",
|
||||
"sha256:702a91ade2968a82beb0db1e0766a6a273f33d4616a6ce8cde475d8e09853b20"
|
||||
],
|
||||
"version": "==0.5.0"
|
||||
},
|
||||
"diff-match-patch": {
|
||||
"hashes": [
|
||||
"sha256:a809a996d0f09b9bbd59e9bbd0b71eed8c807922512910e05cbd3f9480712ddb"
|
||||
],
|
||||
"version": "==20181111"
|
||||
},
|
||||
"django": {
|
||||
"hashes": [
|
||||
"sha256:a32c22af23634e1d11425574dce756098e015a165be02e4690179889b207c7a8",
|
||||
"sha256:d6393918da830530a9516bbbcbf7f1214c3d733738779f06b0f649f49cc698c3"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.1.5"
|
||||
},
|
||||
"django-app-namespace-template-loader": {
|
||||
"hashes": [
|
||||
"sha256:356539413b5d1de0eff91aea7a03806b5ef6874ee5420ea8c273f72bbc601d74",
|
||||
"sha256:7a450985479a2e07fe8a1e4e8208fc9e1d8b35503526dd28eba5f8ad4ba31d4e"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.4.1"
|
||||
},
|
||||
"django-auditlog": {
|
||||
"hashes": [
|
||||
"sha256:70bfc673e7023d91ab8449d745425e7a4ce5eaaf2bdcbfb9b1a2209a7af60b03"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.4.5"
|
||||
},
|
||||
"django-autocomplete-light": {
|
||||
"hashes": [
|
||||
"sha256:5ccb1c8c4b75cf72bc5dabd920190ea1ca3a340f56fb6b12d07a62202837fa75"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.2.10"
|
||||
},
|
||||
"django-bootstrap3": {
|
||||
"hashes": [
|
||||
"sha256:6f7946d513d6340bb70f25d8ec047bf3bf37bab9e499d3baca99b1aa0ec92a52"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==8.2.3"
|
||||
},
|
||||
"django-cors-headers": {
|
||||
"hashes": [
|
||||
"sha256:638aaba85f96af62557656ec559672f03d7c61769685acc405eacfaba9d4e93f",
|
||||
"sha256:c766daf9eefcb9536af9817703ea29124fffee06870f9e523b75144b4d39a694"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.0.1"
|
||||
},
|
||||
"django-filter": {
|
||||
"hashes": [
|
||||
"sha256:6f4e4bc1a11151178520567b50320e5c32f8edb552139d93ea3e30613b886f56",
|
||||
"sha256:86c3925020c27d072cdae7b828aaa5d165c2032a629abbe3c3a1be1edae61c58"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.0.0"
|
||||
},
|
||||
"django-import-export": {
|
||||
"hashes": [
|
||||
"sha256:6e748fcc647fe2a82a55136ebcbe806a45fd7fb5b1e32b33759181f1e67eeb1b",
|
||||
"sha256:7e7ebeb40702eafeb2e770914c01b9961063f472b3b395eeffbea5f39efa7257"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.7.0"
|
||||
},
|
||||
"django-jsonfield": {
|
||||
"hashes": [
|
||||
"sha256:25e53eae8bda165721773145ee0f8ae53b746bb3051b32f2821ba84d79aa77ef",
|
||||
"sha256:813c52463fbbb548fe0d85bf935d0bf72e933fae2bb00ce3ba27bf69ff6fd2ad",
|
||||
"sha256:cacf5a21e7c2490109a60f1122c05aa3858a8dc06952ad764831b4428164fc8e"
|
||||
],
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"django-modeltranslation": {
|
||||
"hashes": [
|
||||
"sha256:254ebda6caea5683407e1fb3e45ceaa2275778c6dc2db2b9d4fec3df373c2cdd"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.13b1"
|
||||
},
|
||||
"django-nocaptcha-recaptcha": {
|
||||
"hashes": [
|
||||
"sha256:d2512d5035d5f62aba5009082db28b28995a7e6f4a46713292e0f4f350f337da"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.0.19"
|
||||
},
|
||||
"django-nose": {
|
||||
"hashes": [
|
||||
"sha256:5df2df802c607daeeab8ac1e93abf54508ed6133eb93852310f512000124b4a5",
|
||||
"sha256:87663f18cb25f01d56c84ac1ff8a0e6e6a6246264b2549b751cb239d0642e76a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.4.5"
|
||||
},
|
||||
"django-phonenumber-field": {
|
||||
"hashes": [
|
||||
"sha256:8db9d2dc833678b163adabd593cda7ad1dede81a1c18f67c895701fc44dc44f1"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.3.0"
|
||||
},
|
||||
"django-suit": {
|
||||
"hashes": [
|
||||
"sha256:19ed865a478dfca81cb5f50a70317700dd70da92c465093251d0e14330a2b92b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.2.26"
|
||||
},
|
||||
"django-tables2": {
|
||||
"hashes": [
|
||||
"sha256:d5d3ad99580121f7ec46ea9e2420069bbd6d2f33b4fde73a376c6bf27d551146"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.6.1"
|
||||
},
|
||||
"djangorestframework": {
|
||||
"hashes": [
|
||||
"sha256:b6714c3e4b0f8d524f193c91ecf5f5450092c2145439ac2769711f7eba89a9d9",
|
||||
"sha256:c375e4f95a3a64fccac412e36fb42ba36881e52313ec021ef410b40f67cddca4"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.8.2"
|
||||
},
|
||||
"djangorestframework-jwt": {
|
||||
"hashes": [
|
||||
"sha256:5efe33032f3a4518a300dc51a51c92145ad95fb6f4b272e5aa24701db67936a7",
|
||||
"sha256:ab15dfbbe535eede8e2e53adaf52ef0cf018ee27dbfad10cbc4cbec2ab63d38c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.11.0"
|
||||
},
|
||||
"et-xmlfile": {
|
||||
"hashes": [
|
||||
"sha256:614d9722d572f6246302c4491846d2c393c199cfa4edc9af593437691683335b"
|
||||
],
|
||||
"version": "==1.0.1"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
|
||||
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
|
||||
],
|
||||
"version": "==2.8"
|
||||
},
|
||||
"ipython": {
|
||||
"hashes": [
|
||||
"sha256:0480354f25b2f443e4ef1456b48f28ad1caaa6d316fca5a5eaa9ca7745ae7923",
|
||||
"sha256:98452af6450e28c9c742d567d75eb6e3a7b391ad4ce8abd5679c5f85ce7fad00",
|
||||
"sha256:d852fed59da67c7e45cb2192027da8bfd920a7856d295c247a45105968d24d5a",
|
||||
"sha256:dba42f182b5f6f26630d2202efd30383712d9f7d8d8d9896b37ae2145deca616"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.2.0"
|
||||
},
|
||||
"ipython-genutils": {
|
||||
"hashes": [
|
||||
"sha256:0c43fa84e93ad0e4dbecaffc6656ac1caf1a48359b2bb0a5da3af84164e3f49b",
|
||||
"sha256:3a0624a251a26463c9dfa0ffa635ec51c4265380980d9a50d65611c3c2bd82a6",
|
||||
"sha256:6218e9abd612fb5acfb175ea7c7b026006de4df9691d9a73c9b390cfa1a41c2b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.1.0"
|
||||
},
|
||||
"jdcal": {
|
||||
"hashes": [
|
||||
"sha256:948fb8d079e63b4be7a69dd5f0cd618a0a57e80753de8248fd786a8a20658a07",
|
||||
"sha256:ea0a5067c5f0f50ad4c7bdc80abad3d976604f6fb026b0b3a17a9d84bb9046c9"
|
||||
],
|
||||
"version": "==1.4"
|
||||
},
|
||||
"lml": {
|
||||
"hashes": [
|
||||
"sha256:b1bef669dc077a1075fa64b99229b6341085b3b3a98d29c66df1853cc14e6c1a",
|
||||
"sha256:ea5ba817b4adc9e9f5c21725cd2475f912933b7e2dfdf0792aed80077154f63f"
|
||||
],
|
||||
"version": "==0.0.9"
|
||||
},
|
||||
"multidict": {
|
||||
"hashes": [
|
||||
"sha256:024b8129695a952ebd93373e45b5d341dbb87c17ce49637b34000093f243dd4f",
|
||||
"sha256:041e9442b11409be5e4fc8b6a97e4bcead758ab1e11768d1e69160bdde18acc3",
|
||||
"sha256:045b4dd0e5f6121e6f314d81759abd2c257db4634260abcfe0d3f7083c4908ef",
|
||||
"sha256:047c0a04e382ef8bd74b0de01407e8d8632d7d1b4db6f2561106af812a68741b",
|
||||
"sha256:068167c2d7bbeebd359665ac4fff756be5ffac9cda02375b5c5a7c4777038e73",
|
||||
"sha256:148ff60e0fffa2f5fad2eb25aae7bef23d8f3b8bdaf947a65cdbe84a978092bc",
|
||||
"sha256:1d1c77013a259971a72ddaa83b9f42c80a93ff12df6a4723be99d858fa30bee3",
|
||||
"sha256:1d48bc124a6b7a55006d97917f695effa9725d05abe8ee78fd60d6588b8344cd",
|
||||
"sha256:31dfa2fc323097f8ad7acd41aa38d7c614dd1960ac6681745b6da124093dc351",
|
||||
"sha256:34f82db7f80c49f38b032c5abb605c458bac997a6c3142e0d6c130be6fb2b941",
|
||||
"sha256:3d5dd8e5998fb4ace04789d1d008e2bb532de501218519d70bb672c4c5a2fc5d",
|
||||
"sha256:4a6ae52bd3ee41ee0f3acf4c60ceb3f44e0e3bc52ab7da1c2b2aa6703363a3d1",
|
||||
"sha256:4b02a3b2a2f01d0490dd39321c74273fed0568568ea0e7ea23e02bd1fb10a10b",
|
||||
"sha256:4b843f8e1dd6a3195679d9838eb4670222e8b8d01bc36c9894d6c3538316fa0a",
|
||||
"sha256:5de53a28f40ef3c4fd57aeab6b590c2c663de87a5af76136ced519923d3efbb3",
|
||||
"sha256:61b2b33ede821b94fa99ce0b09c9ece049c7067a33b279f343adfe35108a4ea7",
|
||||
"sha256:6a3a9b0f45fd75dc05d8e93dc21b18fc1670135ec9544d1ad4acbcf6b86781d0",
|
||||
"sha256:76ad8e4c69dadbb31bad17c16baee61c0d1a4a73bed2590b741b2e1a46d3edd0",
|
||||
"sha256:7ba19b777dc00194d1b473180d4ca89a054dd18de27d0ee2e42a103ec9b7d014",
|
||||
"sha256:7c1b7eab7a49aa96f3db1f716f0113a8a2e93c7375dd3d5d21c4941f1405c9c5",
|
||||
"sha256:7fc0eee3046041387cbace9314926aa48b681202f8897f8bff3809967a049036",
|
||||
"sha256:8ccd1c5fff1aa1427100ce188557fc31f1e0a383ad8ec42c559aabd4ff08802d",
|
||||
"sha256:8e08dd76de80539d613654915a2f5196dbccc67448df291e69a88712ea21e24a",
|
||||
"sha256:c18498c50c59263841862ea0501da9f2b3659c00db54abfbf823a80787fde8ce",
|
||||
"sha256:c49db89d602c24928e68c0d510f4fcf8989d77defd01c973d6cbe27e684833b1",
|
||||
"sha256:ce20044d0317649ddbb4e54dab3c1bcc7483c78c27d3f58ab3d0c7e6bc60d26a",
|
||||
"sha256:d1071414dd06ca2eafa90c85a079169bfeb0e5f57fd0b45d44c092546fcd6fd9",
|
||||
"sha256:d3be11ac43ab1a3e979dac80843b42226d5d3cccd3986f2e03152720a4297cd7",
|
||||
"sha256:db603a1c235d110c860d5f39988ebc8218ee028f07a7cbc056ba6424372ca31b"
|
||||
],
|
||||
"version": "==4.5.2"
|
||||
},
|
||||
"nose": {
|
||||
"hashes": [
|
||||
"sha256:9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac",
|
||||
"sha256:dadcddc0aefbf99eea214e0f1232b94f2fa9bd98fa8353711dacb112bfcbbb2a",
|
||||
"sha256:f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98"
|
||||
],
|
||||
"version": "==1.3.7"
|
||||
},
|
||||
"nose-exclude": {
|
||||
"hashes": [
|
||||
"sha256:f78fa8b41eeb815f0486414f710f1eea0949e346cfb11d59ba6295ed69e84304"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.5.0"
|
||||
},
|
||||
"odfpy": {
|
||||
"hashes": [
|
||||
"sha256:596021f0519623ca8717331951c95e3b8d7b21e86edc7efe8cb650a0d0f59a2b"
|
||||
],
|
||||
"version": "==1.4.0"
|
||||
},
|
||||
"openpyxl": {
|
||||
"hashes": [
|
||||
"sha256:626d38647c063d55803ef4971c4d43226538d4e95cb6260c094e363ee33e10c7"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.4.11"
|
||||
},
|
||||
"pexpect": {
|
||||
"hashes": [
|
||||
"sha256:09b0a7727ce012e0fa668ef848591102d6667521655f4e72d51197c872cb9fb9",
|
||||
"sha256:c381c60f1987355b65df8f08a27f428831914c8a81091bd1778ac336fa2f27e7"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.1.0"
|
||||
},
|
||||
"phonenumberslite": {
|
||||
"hashes": [
|
||||
"sha256:2cb034b158314ca3dc034b417a64777c14b74f27f47b451270686e22eefd57d3",
|
||||
"sha256:fb9212bb8f27ec4bd5ff9a109d4309deed31b45ae5a7216bacc6644759fac82d"
|
||||
],
|
||||
"version": "==8.10.8"
|
||||
},
|
||||
"pickleshare": {
|
||||
"hashes": [
|
||||
"sha256:92ee3b0e21632542ecc9a0a245e69a126f62e5114081bdb0d32e0edd10410033",
|
||||
"sha256:b58cf7d70658a091621c0d8cc35143c8569f3827496b27ed896918c237d05d96"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.7.2"
|
||||
},
|
||||
"pillow": {
|
||||
"hashes": [
|
||||
"sha256:051de330a06c99d6f84bcf582960487835bcae3fc99365185dc2d4f65a390c0e",
|
||||
"sha256:0ae5289948c5e0a16574750021bd8be921c27d4e3527800dc9c2c1d2abc81bf7",
|
||||
"sha256:0b1efce03619cdbf8bcc61cfae81fcda59249a469f31c6735ea59badd4a6f58a",
|
||||
"sha256:163136e09bd1d6c6c6026b0a662976e86c58b932b964f255ff384ecc8c3cefa3",
|
||||
"sha256:18e912a6ccddf28defa196bd2021fe33600cbe5da1aa2f2e2c6df15f720b73d1",
|
||||
"sha256:24ec3dea52339a610d34401d2d53d0fb3c7fd08e34b20c95d2ad3973193591f1",
|
||||
"sha256:267f8e4c0a1d7e36e97c6a604f5b03ef58e2b81c1becb4fccecddcb37e063cc7",
|
||||
"sha256:3273a28734175feebbe4d0a4cde04d4ed20f620b9b506d26f44379d3c72304e1",
|
||||
"sha256:4c678e23006798fc8b6f4cef2eaad267d53ff4c1779bd1af8725cc11b72a63f3",
|
||||
"sha256:4d4bc2e6bb6861103ea4655d6b6f67af8e5336e7216e20fff3e18ffa95d7a055",
|
||||
"sha256:505738076350a337c1740a31646e1de09a164c62c07db3b996abdc0f9d2e50cf",
|
||||
"sha256:5233664eadfa342c639b9b9977190d64ad7aca4edc51a966394d7e08e7f38a9f",
|
||||
"sha256:5d95cb9f6cced2628f3e4de7e795e98b2659dfcc7176ab4a01a8b48c2c2f488f",
|
||||
"sha256:7eda4c737637af74bac4b23aa82ea6fbb19002552be85f0b89bc27e3a762d239",
|
||||
"sha256:801ddaa69659b36abf4694fed5aa9f61d1ecf2daaa6c92541bbbbb775d97b9fe",
|
||||
"sha256:825aa6d222ce2c2b90d34a0ea31914e141a85edefc07e17342f1d2fdf121c07c",
|
||||
"sha256:9c215442ff8249d41ff58700e91ef61d74f47dfd431a50253e1a1ca9436b0697",
|
||||
"sha256:a3d90022f2202bbb14da991f26ca7a30b7e4c62bf0f8bf9825603b22d7e87494",
|
||||
"sha256:a631fd36a9823638fe700d9225f9698fb59d049c942d322d4c09544dc2115356",
|
||||
"sha256:a6523a23a205be0fe664b6b8747a5c86d55da960d9586db039eec9f5c269c0e6",
|
||||
"sha256:a756ecf9f4b9b3ed49a680a649af45a8767ad038de39e6c030919c2f443eb000",
|
||||
"sha256:b117287a5bdc81f1bac891187275ec7e829e961b8032c9e5ff38b70fd036c78f",
|
||||
"sha256:ba04f57d1715ca5ff74bb7f8a818bf929a204b3b3c2c2826d1e1cc3b1c13398c",
|
||||
"sha256:cd878195166723f30865e05d87cbaf9421614501a4bd48792c5ed28f90fd36ca",
|
||||
"sha256:cee815cc62d136e96cf76771b9d3eb58e0777ec18ea50de5cfcede8a7c429aa8",
|
||||
"sha256:d1722b7aa4b40cf93ac3c80d3edd48bf93b9208241d166a14ad8e7a20ee1d4f3",
|
||||
"sha256:d7c1c06246b05529f9984435fc4fa5a545ea26606e7f450bdbe00c153f5aeaad",
|
||||
"sha256:e9c8066249c040efdda84793a2a669076f92a301ceabe69202446abb4c5c5ef9",
|
||||
"sha256:f227d7e574d050ff3996049e086e1f18c7bd2d067ef24131e50a1d3fe5831fbc",
|
||||
"sha256:fc9a12aad714af36cf3ad0275a96a733526571e52710319855628f476dcb144e"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==5.4.1"
|
||||
},
|
||||
"psycopg2-binary": {
|
||||
"hashes": [
|
||||
"sha256:036bcb198a7cc4ce0fe43344f8c2c9a8155aefa411633f426c8c6ed58a6c0426",
|
||||
"sha256:1d770fcc02cdf628aebac7404d56b28a7e9ebec8cfc0e63260bd54d6edfa16d4",
|
||||
"sha256:1fdc6f369dcf229de6c873522d54336af598b9470ccd5300e2f58ee506f5ca13",
|
||||
"sha256:21f9ddc0ff6e07f7d7b6b484eb9da2c03bc9931dd13e36796b111d631f7135a3",
|
||||
"sha256:247873cda726f7956f745a3e03158b00de79c4abea8776dc2f611d5ba368d72d",
|
||||
"sha256:3aa31c42f29f1da6f4fd41433ad15052d5ff045f2214002e027a321f79d64e2c",
|
||||
"sha256:475f694f87dbc619010b26de7d0fc575a4accf503f2200885cc21f526bffe2ad",
|
||||
"sha256:4b5e332a24bf6e2fda1f51ca2a57ae1083352293a08eeea1fa1112dc7dd542d1",
|
||||
"sha256:570d521660574aca40be7b4d532dfb6f156aad7b16b5ed62d1534f64f1ef72d8",
|
||||
"sha256:59072de7def0690dd13112d2bdb453e20570a97297070f876fbbb7cbc1c26b05",
|
||||
"sha256:5f0b658989e918ef187f8a08db0420528126f2c7da182a7b9f8bf7f85144d4e4",
|
||||
"sha256:649199c84a966917d86cdc2046e03d536763576c0b2a756059ae0b3a9656bc20",
|
||||
"sha256:6645fc9b4705ae8fbf1ef7674f416f89ae1559deec810f6dd15197dfa52893da",
|
||||
"sha256:6872dd54d4e398d781efe8fe2e2d7eafe4450d61b5c4898aced7610109a6df75",
|
||||
"sha256:6ce34fbc251fc0d691c8d131250ba6f42fd2b28ef28558d528ba8c558cb28804",
|
||||
"sha256:73920d167a0a4d1006f5f3b9a3efce6f0e5e883a99599d38206d43f27697df00",
|
||||
"sha256:8a671732b87ae423e34b51139628123bc0306c2cb85c226e71b28d3d57d7e42a",
|
||||
"sha256:8d517e8fda2efebca27c2018e14c90ed7dc3f04d7098b3da2912e62a1a5585fe",
|
||||
"sha256:9475a008eb7279e20d400c76471843c321b46acacc7ee3de0b47233a1e3fa2cf",
|
||||
"sha256:96947b8cd7b3148fb0e6549fcb31258a736595d6f2a599f8cd450e9a80a14781",
|
||||
"sha256:abf229f24daa93f67ac53e2e17c8798a71a01711eb9fcdd029abba8637164338",
|
||||
"sha256:b1ab012f276df584beb74f81acb63905762c25803ece647016613c3d6ad4e432",
|
||||
"sha256:b22b33f6f0071fe57cb4e9158f353c88d41e739a3ec0d76f7b704539e7076427",
|
||||
"sha256:b3b2d53274858e50ad2ffdd6d97ce1d014e1e530f82ec8b307edd5d4c921badf",
|
||||
"sha256:bab26a729befc7b9fab9ded1bba9c51b785188b79f8a2796ba03e7e734269e2e",
|
||||
"sha256:daa1a593629aa49f506eddc9d23dc7f89b35693b90e1fbcd4480182d1203ea90",
|
||||
"sha256:dd111280ce40e89fd17b19c1269fd1b74a30fce9d44a550840e86edb33924eb8",
|
||||
"sha256:e0b86084f1e2e78c451994410de756deba206884d6bed68d5a3d7f39ff5fea1d",
|
||||
"sha256:eb86520753560a7e89639500e2a254bb6f683342af598088cb72c73edcad21e6",
|
||||
"sha256:ff18c5c40a38d41811c23e2480615425c97ea81fd7e9118b8b899c512d97c737"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.7.6.1"
|
||||
},
|
||||
"ptyprocess": {
|
||||
"hashes": [
|
||||
"sha256:0530ce63a9295bfae7bd06edc02b6aa935619f486f0f1dc0972f516265ee81a6",
|
||||
"sha256:464cb76f7a7122743dd25507650db89cd447c51f38e4671602b3eaa2e38e05ae"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.5.1"
|
||||
},
|
||||
"pycodestyle": {
|
||||
"hashes": [
|
||||
"sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766",
|
||||
"sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.3.1"
|
||||
},
|
||||
"pyexcel": {
|
||||
"hashes": [
|
||||
"sha256:2a32accc28aea3994922606ecf7bef00ef058b56b1bea6af119ae3bb56468333",
|
||||
"sha256:f1ffe613f09285edf42132b9afc14f81adbd0f56797e5fe05e98307d00ce175f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.5.10"
|
||||
},
|
||||
"pyexcel-io": {
|
||||
"hashes": [
|
||||
"sha256:55b2aa4ef81ba6e3285edfb1a3f3c3c69f9f4d52b6867318ae2381f88741143f",
|
||||
"sha256:de9de0d6bf9a8906c94e3b5dbe0b3a3e0a9bc893201d6a5c5b3cf84e5119d60d"
|
||||
],
|
||||
"version": "==0.5.16"
|
||||
},
|
||||
"pyexcel-xlsx": {
|
||||
"hashes": [
|
||||
"sha256:488783c3f5195bed8638f6064b11d97f706641b0f065a5416297a01db6cec5ea",
|
||||
"sha256:b3566162f7232336ebe0d40dd298145c18715009b020dddc210890cf6436ffb2"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.5.5"
|
||||
},
|
||||
"pyjwt": {
|
||||
"hashes": [
|
||||
"sha256:30b1380ff43b55441283cc2b2676b755cca45693ae3097325dea01f3d110628c",
|
||||
"sha256:4ee413b357d53fd3fb44704577afac88e72e878716116270d722723d65b42176"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.6.4"
|
||||
},
|
||||
"python-dateutil": {
|
||||
"hashes": [
|
||||
"sha256:3acbef017340600e9ff8f2994d8f7afd6eacb295383f286466a6df3961e486f0",
|
||||
"sha256:537bf2a8f8ce6f6862ad705cd68f9e405c0b5db014aa40fa29eab4335d4b1716",
|
||||
"sha256:62a2f8df3d66f878373fd0072eacf4ee52194ba302e00082828e0d263b0418d2"
|
||||
],
|
||||
"version": "==2.6.0"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:8781cdd3ca70f5a536884e051797ca213b9ff479a5c1cc57240adf37cc1eff1b",
|
||||
"sha256:be2ff04e94a2b5454ddcfbebb81ee8e46162734d4c2fcc90c422d16ab51f810b",
|
||||
"sha256:c823de61ff40d1996fe087cec343e0503881ca641b897e0f9b86c7683a0bfee1",
|
||||
"sha256:ee7c751544e35a7b7fb5e3fb25a49dade37d51e70a93e5107f10575d7102c311"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2016.4"
|
||||
},
|
||||
"pyyaml": {
|
||||
"hashes": [
|
||||
"sha256:1adecc22f88d38052fb787d959f003811ca858b799590a5eaa70e63dca50308c",
|
||||
"sha256:436bc774ecf7c103814098159fbb84c2715d25980175292c648f2da143909f95",
|
||||
"sha256:460a5a4248763f6f37ea225d19d5c205677d8d525f6a83357ca622ed541830c2",
|
||||
"sha256:5a22a9c84653debfbf198d02fe592c176ea548cccce47553f35f466e15cf2fd4",
|
||||
"sha256:7a5d3f26b89d688db27822343dfa25c599627bc92093e788956372285c6298ad",
|
||||
"sha256:9372b04a02080752d9e6f990179a4ab840227c6e2ce15b95e1278456664cf2ba",
|
||||
"sha256:a5dcbebee834eaddf3fa7366316b880ff4062e4bcc9787b78c7fbb4a26ff2dd1",
|
||||
"sha256:aee5bab92a176e7cd034e57f46e9df9a9862a71f8f37cad167c6fc74c65f5b4e",
|
||||
"sha256:c51f642898c0bacd335fc119da60baae0824f2cde95b0330b56c0553439f0673",
|
||||
"sha256:c68ea4d3ba1705da1e0d85da6684ac657912679a649e8868bd850d2c299cce13",
|
||||
"sha256:e23d0cc5299223dcc37885dae624f382297717e459ea24053709675a976a3e19"
|
||||
],
|
||||
"version": "==5.1"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:545c4855cd9d7c12671444326337013766f4eea6068c3f0307fb2dc2696d580e",
|
||||
"sha256:5acf980358283faba0b897c73959cecf8b841205bb4b2ad3ef545f46eae1a133"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.11.1"
|
||||
},
|
||||
"simplegeneric": {
|
||||
"hashes": [
|
||||
"sha256:dc972e06094b9af5b855b3df4a646395e43d1c9d0d39ed345b7393560d0b9173"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.8.1"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:0ff78c403d9bccf5a425a6d31a12aa6b47f1c21ca4dc2573a7e2f32a97335eb1",
|
||||
"sha256:105f8d68616f8248e24bf0e9372ef04d3cc10104f1980f54d57b2ce73a5ad56a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.10.0"
|
||||
},
|
||||
"tablib": {
|
||||
"hashes": [
|
||||
"sha256:0f88a9cebdaa1a2cc29ae57387082ee81015d1149ecd34e48a8c8d3b4dd21670",
|
||||
"sha256:5f33c079b07eb10cf9c4b4696add2ecf32c89db7729240546ecdcd5c92f67e13"
|
||||
],
|
||||
"version": "==0.13.0"
|
||||
},
|
||||
"telepot": {
|
||||
"hashes": [
|
||||
"sha256:8910fd6fb708e2c3ded7ca82cc945a645b717699d9f82ddff5123bb2e05f780f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==12.3"
|
||||
},
|
||||
"texttable": {
|
||||
"hashes": [
|
||||
"sha256:2b60a5304ccfbeac80ffae7350d7c2f5d7a24e9aab5036d0f82489746419d9b2"
|
||||
],
|
||||
"version": "==1.6.1"
|
||||
},
|
||||
"traitlets": {
|
||||
"hashes": [
|
||||
"sha256:05a66843c96a320eec09df674c16ff330a43cb07f731cf2bd88aa3645a180541",
|
||||
"sha256:76eba33c89723b8fc024f950cacaf5bf2ef37999642cc9a61f4e7c1ca5cf0ac0",
|
||||
"sha256:d6db3201395f9b955786d25a1817c07291e2bcb96eb7f41683ae3836836179d7"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.2.1"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39",
|
||||
"sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22"
|
||||
],
|
||||
"version": "==1.24.1"
|
||||
},
|
||||
"xlrd": {
|
||||
"hashes": [
|
||||
"sha256:546eb36cee8db40c3eaa46c351e67ffee6eeb5fa2650b71bc4c758a29a1b29b2",
|
||||
"sha256:e551fb498759fa3a5384a94ccd4c3c02eb7c00ea424426e212ac0c57be9dfbde"
|
||||
],
|
||||
"version": "==1.2.0"
|
||||
},
|
||||
"xlwt": {
|
||||
"hashes": [
|
||||
"sha256:a082260524678ba48a297d922cc385f58278b8aa68741596a87de01a9c628b2e",
|
||||
"sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88"
|
||||
],
|
||||
"version": "==1.3.0"
|
||||
},
|
||||
"yarl": {
|
||||
"hashes": [
|
||||
"sha256:024ecdc12bc02b321bc66b41327f930d1c2c543fa9a561b39861da9388ba7aa9",
|
||||
"sha256:2f3010703295fbe1aec51023740871e64bb9664c789cba5a6bdf404e93f7568f",
|
||||
"sha256:3890ab952d508523ef4881457c4099056546593fa05e93da84c7250516e632eb",
|
||||
"sha256:3e2724eb9af5dc41648e5bb304fcf4891adc33258c6e14e2a7414ea32541e320",
|
||||
"sha256:5badb97dd0abf26623a9982cd448ff12cb39b8e4c94032ccdedf22ce01a64842",
|
||||
"sha256:73f447d11b530d860ca1e6b582f947688286ad16ca42256413083d13f260b7a0",
|
||||
"sha256:7ab825726f2940c16d92aaec7d204cfc34ac26c0040da727cf8ba87255a33829",
|
||||
"sha256:b25de84a8c20540531526dfbb0e2d2b648c13fd5dd126728c496d7c3fea33310",
|
||||
"sha256:c6e341f5a6562af74ba55205dbd56d248daf1b5748ec48a0200ba227bb9e33f4",
|
||||
"sha256:c9bb7c249c4432cd47e75af3864bc02d26c9594f49c82e2a28624417f0ae63b8",
|
||||
"sha256:e060906c0c585565c718d1c3841747b61c5439af2211e185f6739a9412dfbde1"
|
||||
],
|
||||
"version": "==1.3.0"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"autopep8": {
|
||||
"hashes": [
|
||||
"sha256:33d2b5325b7e1afb4240814fe982eea3a92ebea712869bfd08b3c0393404248c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.4.3"
|
||||
},
|
||||
"entrypoints": {
|
||||
"hashes": [
|
||||
"sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19",
|
||||
"sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"
|
||||
],
|
||||
"version": "==0.3"
|
||||
},
|
||||
"flake8": {
|
||||
"hashes": [
|
||||
"sha256:859996073f341f2670741b51ec1e67a01da142831aa1fdc6242dbf88dffbe661",
|
||||
"sha256:a796a115208f5c03b18f332f7c11729812c8c3ded6c46319c59b53efd3819da8"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.7.7"
|
||||
},
|
||||
"mccabe": {
|
||||
"hashes": [
|
||||
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
|
||||
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
|
||||
],
|
||||
"version": "==0.6.1"
|
||||
},
|
||||
"pycodestyle": {
|
||||
"hashes": [
|
||||
"sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766",
|
||||
"sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.3.1"
|
||||
},
|
||||
"pyflakes": {
|
||||
"hashes": [
|
||||
"sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0",
|
||||
"sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"
|
||||
],
|
||||
"version": "==2.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
+53
-34
@@ -16,10 +16,6 @@ Data table app for viewing and modifying the member register, member application
|
||||
|
||||
Mostly static website with an event calendar and news feed.
|
||||
|
||||
### Coffee scale
|
||||
|
||||
Shows the current coffee scale status.
|
||||
|
||||
## Accessing the source
|
||||
|
||||
### Clone this repository and enter it
|
||||
@@ -30,18 +26,47 @@ Set up your SSH key authentication in GitLab Profile Settings. Then clone the re
|
||||
git clone git@gitlab.com:sahkoinsinoorikilta/vtmk/web2.0-backend.git
|
||||
cd web2.0-backend
|
||||
git checkout develop
|
||||
cp scripts/git/pre-push .git/hooks/pre-push # install a script to test code before committing
|
||||
```
|
||||
|
||||
## Windows install instructions
|
||||
## Development
|
||||
|
||||
See [Windows install instructions](./windows_install.md)
|
||||
### Poetry
|
||||
|
||||
## Linux/Mac install instructions
|
||||
For depedencies and virtual environment, we use [poetry](https://python-poetry.org/).
|
||||
|
||||
See [Linux/Mac install instructions](./linux_install.md)
|
||||
```bash
|
||||
python3 -m pip install poetry
|
||||
```
|
||||
|
||||
## Initializing data
|
||||
The easiest integration with VSCode is to have poetry install virtual environment in project folder, configured with CMD
|
||||
|
||||
```bash
|
||||
python3 -m poetry config virtualenvs.in-project true
|
||||
```
|
||||
|
||||
Start developing by install dependencies first
|
||||
|
||||
#### CMDs
|
||||
|
||||
Activate virtual environment in shell
|
||||
|
||||
```bash
|
||||
python3 -m poetry shell
|
||||
```
|
||||
|
||||
Install dependencies
|
||||
|
||||
```bash
|
||||
poetry install
|
||||
```
|
||||
|
||||
### npm scripts
|
||||
|
||||
We use Node.js for few development tasks, like linting. Easiest way to install Node is [nvm](https://github.com/nvm-sh/nvm).
|
||||
|
||||
TODO: List scripts
|
||||
|
||||
### Initializing data
|
||||
|
||||
Run the following `manage.py` commands. Do not run these in production without thinking!
|
||||
|
||||
@@ -51,33 +76,23 @@ python manage.py initialize # creates user groups
|
||||
python manage.py createdummydata # creates dummy members to the member register
|
||||
```
|
||||
|
||||
## Running
|
||||
### Running
|
||||
|
||||
### Use runserver command
|
||||
```bash
|
||||
python manage.py runserver
|
||||
```
|
||||
|
||||
#### Visit the page
|
||||
|
||||
Visit [https://localhost:8000](https://localhost:8000) in your browser!
|
||||
|
||||
Using address `0.0.0.0` will bind to all IP addresses. Using `localhost` will only bind to your machine.
|
||||
|
||||
```bash
|
||||
python manage.py runserver 0.0.0.0:8000
|
||||
```
|
||||
|
||||
Using address `0.0.0.0` will bind to all IP addresses. Using `localhost` will only bind to your machine.
|
||||
|
||||
### Visit the page
|
||||
|
||||
Visit [https://localhost:8000](https://localhost:8000) in your browser!
|
||||
|
||||
## Running in production
|
||||
|
||||
Run the project in production with gunicorn. Refer to [this page](https://www.digitalocean.com/community/tutorials/how-to-set-up-django-with-postgres-nginx-and-gunicorn-on-ubuntu-16-04) for instructions.
|
||||
|
||||
Install production dependencies.
|
||||
|
||||
```bash
|
||||
pip install -r requirements.production.txt
|
||||
```
|
||||
|
||||
## Development workflow
|
||||
|
||||
Do not use `rebase` when pulling or merging changes. Rebasing transforms the commit history and makes it appear more linear. This is a plus e.g. when working with a few people, but in this project traditional merging is recommended for clarity.
|
||||
### Development workflow
|
||||
|
||||
When you start working on a feature, create a feature branch for your changes. These feature branches should be prefixed with `feature`.
|
||||
|
||||
@@ -98,7 +113,7 @@ Merge requests to `master` should be reviewed by multiple developers. Only a mod
|
||||
Lint python files using `pycodestyle` with
|
||||
|
||||
```bash
|
||||
pycodestyle --config=setup.cfg --count .
|
||||
pycodestyle --config=pycodestyle.cfg --count .
|
||||
```
|
||||
|
||||
Lint javascript and markdown using `eslint` and `remark` with
|
||||
@@ -114,13 +129,17 @@ Use an editor with linting capabilities to write pretty code that passes linting
|
||||
Run unit tests with
|
||||
|
||||
```bash
|
||||
python manage.py test -v 2
|
||||
python manage.py test
|
||||
```
|
||||
|
||||
Due to the mostly static nature of the project, most elements are difficult to properly unit test. If you write code with actual logic, make sure to write at least one unit or integration test that tests your code's core functionality.
|
||||
|
||||
Tests are located in `tests.py` under every subproject.
|
||||
|
||||
### GitLab CI
|
||||
## Production
|
||||
|
||||
Project is run in production with Docker. See `Dockerfile` for details.
|
||||
|
||||
## GitLab CI
|
||||
|
||||
All pushed changes go through the GitLab Continuous Integration, which consists of automated unit testing and linting. Make sure your changes pass both before merging to `develop` or `master`.
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
@@ -1,124 +0,0 @@
|
||||
body {
|
||||
background-color: white;
|
||||
font-family: monospace;
|
||||
color: black;
|
||||
}
|
||||
#container{
|
||||
position:relative;
|
||||
width:95%;
|
||||
margin-left:auto;
|
||||
margin-right:auto;
|
||||
height:100%;
|
||||
overflow:hidden;
|
||||
|
||||
}
|
||||
#upper{
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: bottom center;
|
||||
background-image: url("/static/coffee_scale/img/smokes.png");
|
||||
transform-origin: bottom;
|
||||
animation: smokes 8s ease-in-out 0s infinite;
|
||||
opacity:0;
|
||||
height:40%;
|
||||
}
|
||||
#lower{
|
||||
position:relative;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: top center;
|
||||
background-image: url("/static/coffee_scale/img/coffeecup3.png");
|
||||
height:60%;
|
||||
}
|
||||
#scale{
|
||||
position:absolute;
|
||||
top:80%;
|
||||
width:90%;
|
||||
height:10%;
|
||||
margin: 0% 5% 0% 5%;
|
||||
background: lightgrey;
|
||||
border-radius: 10px;
|
||||
overflow:hidden;
|
||||
}
|
||||
#scale2{
|
||||
width: 0%;
|
||||
transition: width 2s;
|
||||
height:100%;
|
||||
background: green;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.brewtime{
|
||||
text-align:right;
|
||||
position:absolute;
|
||||
right:0px;
|
||||
z-index:5;
|
||||
font-size:10vw;
|
||||
}
|
||||
#address{
|
||||
text-align:left;
|
||||
position:absolute;
|
||||
left:0px;
|
||||
z-index:5;
|
||||
font-size:4vw;
|
||||
color: #333;
|
||||
}
|
||||
.layertwo{
|
||||
display: None;
|
||||
}
|
||||
noscript{
|
||||
color:red;
|
||||
}
|
||||
.text{
|
||||
color:green;
|
||||
position:absolute;
|
||||
top:50%;
|
||||
left:50%;
|
||||
}
|
||||
.brewing{
|
||||
animation: brewing 5s ease-in-out 0s infinite;
|
||||
}
|
||||
.hurry{
|
||||
color:red !important;
|
||||
}
|
||||
.unknown{
|
||||
color:orange !important;
|
||||
animation: unknown 5s ease-in-out 0s infinite;
|
||||
}
|
||||
.friday{
|
||||
animation: friday 20s ease-in-out 0s infinite;
|
||||
}
|
||||
.normal{
|
||||
animation: normal 1000s ease-in-out 0s infinite;
|
||||
}
|
||||
.coffeeready{
|
||||
animation: coffeeready 10s ease-in-out 0s;
|
||||
}
|
||||
@keyframes smokes {
|
||||
0% {transform: skewX(-10deg);}
|
||||
50% {transform: skewX(10deg);}
|
||||
100% {transform: skewX(-10deg);}
|
||||
}
|
||||
@keyframes brewing {
|
||||
0% {color:green;}
|
||||
50% {color: transparent;}
|
||||
100% {color:green;}
|
||||
}
|
||||
@keyframes coffeeready {
|
||||
0% {background-color:white;}
|
||||
25% {background-color:rgb(100, 255, 100);}
|
||||
50% {background-color:white;}
|
||||
75% {background-color:rgb(100, 255, 100);}
|
||||
100% {background-color:white;}
|
||||
}
|
||||
@keyframes unknown {
|
||||
0%,40% {transform: rotate(0deg);}
|
||||
60%,100% {transform: rotate(360deg);}
|
||||
}
|
||||
@keyframes friday {
|
||||
0% {transform: rotate(0deg);}
|
||||
100% {transform: rotate(360deg);}
|
||||
}
|
||||
@keyframes normal {
|
||||
0%,49% {transform: rotate(0deg);}
|
||||
50%,100% {transform: rotate(360deg);}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.0 KiB |
@@ -1,183 +0,0 @@
|
||||
//Inner state
|
||||
var lastBrew = new Date(0);
|
||||
var brewing = false;
|
||||
var backoff = 2000;
|
||||
|
||||
//MQTT client config
|
||||
var username = "coffee-user-"+ Math.random();
|
||||
// eslint-disable-next-line no-undef
|
||||
var client = new Paho.MQTT.Client("sika.sahkoinsinoorikilta.fi", 9001, username);
|
||||
client.onMessageArrived = function (message) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("Topic: "+message.destinationName+" msg: "+message.payloadString);
|
||||
var ev = new CustomEvent(message.destinationName, {'detail': message.payloadString});
|
||||
window.dispatchEvent(ev);
|
||||
}
|
||||
|
||||
function reconnect(responseObject){
|
||||
if (responseObject.errorCode !== 0) {
|
||||
console.log("connection lost! Reason: "+responseObject.errorMessage); // eslint-disable-line no-console
|
||||
setTimeout(function(){
|
||||
client.connect({onSuccess:onConnect, useSSL:true, onFailure: reconnect});
|
||||
}, backoff);
|
||||
}
|
||||
}
|
||||
|
||||
function onConnect() {
|
||||
console.log("MQTT connected"); // eslint-disable-line no-console
|
||||
//set and reset reconnector
|
||||
client.onConnectionLost = reconnect
|
||||
// subscribe to topics
|
||||
client.subscribe("sik/kiltahuone/kahvivaaka/cups");
|
||||
client.subscribe("sik/kiltahuone/kahvivaaka/brewing");
|
||||
client.subscribe("sik/kiltahuone/kahvivaaka/brewtime");
|
||||
}
|
||||
|
||||
// data update and parse functions
|
||||
function parseCups(ev){
|
||||
var cups = parseFloat(ev.detail).toFixed(1)
|
||||
|
||||
function makeEvent(cups) {
|
||||
return (String(cups) !== '-1.0')
|
||||
? new CustomEvent("cupsChanged", {'detail': cups})
|
||||
: new CustomEvent("cupsError", {'detail': 'Error: unable to fetch cups :('});
|
||||
}
|
||||
|
||||
window.dispatchEvent(makeEvent(cups));
|
||||
}
|
||||
function updateCups(ev){
|
||||
$("#text").text(ev.detail);
|
||||
}
|
||||
function showCupsError(ev) {
|
||||
$('#text').text(ev.detail);
|
||||
$('#text').css({
|
||||
'font-size': '7vh',
|
||||
'left': '0',
|
||||
'top': '40%',
|
||||
'width': '100%',
|
||||
'text-align': 'center',
|
||||
'color': 'red',
|
||||
});
|
||||
$('#lower').css({'background-image': 'none'});
|
||||
}
|
||||
function updateScale(ev){
|
||||
$("#scale2").css({width: Math.min(ev.detail/9*100,100) + '%'});
|
||||
}
|
||||
|
||||
function tick(){
|
||||
var ev = new CustomEvent("tick", {'detail': new Date()});
|
||||
window.dispatchEvent(ev);
|
||||
}
|
||||
|
||||
function updateTime(ev){
|
||||
var now = ev.detail;
|
||||
$("#time").html(formatTime(now.getHours(),now.getMinutes(),now.getSeconds()));
|
||||
}
|
||||
|
||||
function coffeeLowEffect(ev){
|
||||
ev.detail <= 2 ? $("#text").addClass("hurry") : $("#text").removeClass("hurry");
|
||||
}
|
||||
function coffeeReadyEffect(){
|
||||
$("body").addClass("coffeeready");
|
||||
// autoclear animation class in 10s
|
||||
setTimeout(function(){$("body").removeClass("coffeeready");}, 10000);
|
||||
}
|
||||
function hotEffect(ev){
|
||||
var opa = Math.max(100 - ev.detail / 90000,0);
|
||||
$("#upper").css({opacity: opa/100});
|
||||
}
|
||||
function brewAnimStart(){
|
||||
$(".text").addClass("brewing");
|
||||
$(".layerone").hide();
|
||||
$(".layertwo").show();
|
||||
}
|
||||
function brewAnimEnd(){
|
||||
$(".text").removeClass("brewing");
|
||||
$(".layertwo").hide();
|
||||
$(".layerone").show();
|
||||
}
|
||||
function brewNotifier(ev){
|
||||
var new_brewing = parseInt(ev.detail);
|
||||
if (new_brewing == 1 && brewing == 0){
|
||||
window.dispatchEvent(new Event("brewStart"));
|
||||
} else if (new_brewing == 0 && brewing == 1){
|
||||
window.dispatchEvent(new Event("brewEnd"));
|
||||
}
|
||||
brewing = new_brewing;
|
||||
}
|
||||
function brewTimeParser(ev){
|
||||
lastBrew = new Date(parseInt(ev.detail)*1000.0);
|
||||
}
|
||||
function updateBrewDiff(){
|
||||
var now = new Date();
|
||||
var timeDiff = Math.max(now.getTime() - lastBrew.getTime(), 0);
|
||||
var eve = new CustomEvent("dtUpdate", {'detail': timeDiff});
|
||||
window.dispatchEvent(eve);
|
||||
}
|
||||
function updateBrewTime(ev){
|
||||
var timeDiff = ev.detail;
|
||||
var timeStr;
|
||||
if (timeDiff < 3600000){
|
||||
timeStr = Math.round(timeDiff / 60000) + ' min'
|
||||
} else if (timeDiff < 10000* 3600 * 1000){ // 1000h
|
||||
timeStr = '~' + Math.round(timeDiff / 3600000 * 2) / 2 + ' h';
|
||||
} else {
|
||||
timeStr = "???"
|
||||
}
|
||||
$("#brewtime").html(timeStr);
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
function nToS(num){
|
||||
return num < 10 ? "0" + num : "" + num;
|
||||
}
|
||||
|
||||
function formatTime(hours, minutes, seconds){
|
||||
return nToS(hours)+":"+nToS(minutes)+":"+nToS(seconds)
|
||||
}
|
||||
|
||||
function resize(){
|
||||
var w = $("#container").width();
|
||||
var h = $("#container").height();
|
||||
var s = w > h ? h : w;
|
||||
var font = s * 0.8 * 0.38/Math.sqrt(3);
|
||||
$(".text").css({ top: s*0.16-font/2 + 'px',
|
||||
fontSize: font + 'px',
|
||||
marginLeft: -font*3*3/10 + 'px'});
|
||||
}
|
||||
|
||||
// Init everything
|
||||
|
||||
$(document).ready(function(){
|
||||
client.connect({onSuccess:onConnect, useSSL:true, onFailure:reconnect});
|
||||
|
||||
//connect MQTT event listeners
|
||||
window.addEventListener("sik/kiltahuone/kahvivaaka/cups", parseCups);
|
||||
window.addEventListener("sik/kiltahuone/kahvivaaka/brewing", brewNotifier);
|
||||
window.addEventListener("sik/kiltahuone/kahvivaaka/brewtime", brewTimeParser);
|
||||
|
||||
//connect other event listeners
|
||||
window.addEventListener("cupsChanged", updateCups);
|
||||
window.addEventListener("cupsChanged", coffeeLowEffect);
|
||||
window.addEventListener("cupsChanged", updateScale);
|
||||
window.addEventListener("cupsChanged", resize);
|
||||
|
||||
window.addEventListener("cupsError", showCupsError);
|
||||
window.addEventListener("cupsError", coffeeLowEffect);
|
||||
window.addEventListener("cupsError", updateScale);
|
||||
|
||||
window.addEventListener("brewStart", brewAnimStart);
|
||||
window.addEventListener("brewEnd", brewAnimEnd);
|
||||
window.addEventListener("brewEnd", coffeeReadyEffect);
|
||||
window.addEventListener("tick", updateTime);
|
||||
window.addEventListener("tick", updateBrewDiff);
|
||||
window.addEventListener("dtUpdate", updateBrewTime);
|
||||
window.addEventListener("dtUpdate", hotEffect);
|
||||
|
||||
//start time based events
|
||||
setInterval(tick, 100);
|
||||
tick();
|
||||
|
||||
});
|
||||
$(window).resize(resize);
|
||||
@@ -1,41 +0,0 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Coffee Cups @Guild Room - AYY SIK ry</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="3600">
|
||||
<meta http-equiv="cache-control" content="max-age=0" />
|
||||
<meta http-equiv="cache-control" content="no-cache" />
|
||||
<meta http-equiv="expires" content="0" />
|
||||
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
|
||||
<meta http-equiv="pragma" content="no-cache" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.2/mqttws31.js"
|
||||
type="text/javascript"></script>
|
||||
<link rel="stylesheet" href="{% static "coffee_scale/css/coffee.css" %}">
|
||||
<script src="{% static "coffee_scale/js/coffee.js" %}"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="container">
|
||||
<span id="brewtime" class="brewtime layerone"></span>
|
||||
<span class="brewtime layertwo">:)</span>
|
||||
<span id="address">
|
||||
ka.dy.fi
|
||||
<noscript><br>This page uses JavaScript!</noscript>
|
||||
<br><span id="time"></span></span>
|
||||
</span>
|
||||
<div id="upper">
|
||||
</div>
|
||||
<!--Kahvinkeitin on rikki. Varakeittimellä keitettyä kahvia saattaa olla.-->
|
||||
<div id="lower" class="normal">
|
||||
<div id="text" class="text layerone">???</div>
|
||||
<div class="text layertwo"> +</div>
|
||||
<div id="scale"><div id="scale2"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,2 +0,0 @@
|
||||
from django.test import TestCase, Client
|
||||
from django.conf import settings
|
||||
@@ -1,12 +0,0 @@
|
||||
from django.conf.urls import url
|
||||
from django.conf import settings
|
||||
from .views import coffee_view
|
||||
|
||||
urlpatterns = [
|
||||
# landing page
|
||||
url(r'^$', coffee_view),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
urlpatterns += staticfiles_urlpatterns()
|
||||
@@ -1,11 +0,0 @@
|
||||
from django.shortcuts import render
|
||||
from django.http import JsonResponse
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
import logging
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
def coffee_view(request):
|
||||
return render(request, 'coffee_scale:coffee.html')
|
||||
+13
-2
@@ -2,12 +2,23 @@ version: '3'
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres
|
||||
image: postgres:12
|
||||
volumes:
|
||||
- dbdata:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
|
||||
web:
|
||||
build: .
|
||||
image: registry.gitlab.com/sahkoinsinoorikilta/vtmk/web2.0-backend
|
||||
command: ["bash", "-c", "cd /code && ./wait-for-it.sh db:5432 -- bash setup.sh --no-input --no-npm && python manage.py runserver 0.0.0.0:8000"]
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "8000:8000"
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
volumes:
|
||||
dbdata:
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@@ -1,5 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ExpensesClaimConfig(AppConfig):
|
||||
name = 'expenses_claim'
|
||||
@@ -1,30 +0,0 @@
|
||||
"""Expenses claim form."""
|
||||
|
||||
from django import forms
|
||||
from string import ascii_uppercase
|
||||
|
||||
|
||||
class ExpensesClaim(forms.Form):
|
||||
"""Expenses claim form"""
|
||||
|
||||
name = forms.CharField(label='Nimi', max_length=100)
|
||||
iban = forms.CharField(label='IBAN', max_length=100)
|
||||
amount = forms.DecimalField(label="Summa", decimal_places=2)
|
||||
|
||||
def clean_iban(self):
|
||||
"""Validate IBAN."""
|
||||
data = self.cleaned_data['iban']
|
||||
# Remove spaces.
|
||||
data = data.replace(" ", "")
|
||||
# Move first 4 symbols to the end of the string.
|
||||
data = data[4:] + data[0:4]
|
||||
LETTERS = {letter: str(index) for index,
|
||||
letter in enumerate(ascii_uppercase, start=10)}
|
||||
data = data.upper()
|
||||
# Replace all letters with numbers, so that A=10, B=11, ..., Z=35.
|
||||
data = [LETTERS[char] if char in LETTERS else char for char in data]
|
||||
data = ''.join(data)
|
||||
# If data modulo 97 != 1 the IBAN number is invalid.
|
||||
if int(data) % 97 != 1:
|
||||
raise forms.ValidationError("Invalid IBAN number!")
|
||||
return data
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
@@ -1,7 +0,0 @@
|
||||
Dis is claim
|
||||
|
||||
<form action="" method="post">
|
||||
{{ form }}
|
||||
<input type="submit" value="Submit">
|
||||
{% csrf_token %}
|
||||
</form>
|
||||
@@ -1,54 +0,0 @@
|
||||
from django.test import TestCase
|
||||
from .forms import ExpensesClaim
|
||||
|
||||
|
||||
class ExpensesClaimTest(TestCase):
|
||||
"""Test expenses claim form."""
|
||||
|
||||
def test_valid_data1(self):
|
||||
form = ExpensesClaim({
|
||||
'name': "John Doe",
|
||||
'iban': "FI37 1590 3000 0007 76",
|
||||
'amount': 12.54
|
||||
})
|
||||
self.assertTrue(form.is_valid())
|
||||
|
||||
def test_valid_data2(self):
|
||||
form = ExpensesClaim({
|
||||
'name': "John Cena",
|
||||
'iban': "AL35202111090000000001234567",
|
||||
'amount': 12
|
||||
})
|
||||
self.assertTrue(form.is_valid())
|
||||
|
||||
def test_valid_data3(self):
|
||||
form = ExpensesClaim({
|
||||
'name': "John Wayne",
|
||||
'iban': "BR1500000000000010932840814P2",
|
||||
'amount': 12.0
|
||||
})
|
||||
self.assertTrue(form.is_valid())
|
||||
|
||||
def test_invalid_iban(self):
|
||||
form = ExpensesClaim({
|
||||
'name': "John Lennon",
|
||||
'iban': "FI3734 1590 3000 0007 76",
|
||||
'amount': 12.54
|
||||
})
|
||||
self.assertFalse(form.is_valid())
|
||||
|
||||
def test_invalid_amount(self):
|
||||
form = ExpensesClaim({
|
||||
'name': "John Kenedy",
|
||||
'iban': "FI37 1590 3000 0007 76",
|
||||
'amount': "asd"
|
||||
})
|
||||
self.assertFalse(form.is_valid())
|
||||
|
||||
def test_invalid_amount_decimal_places(self):
|
||||
form = ExpensesClaim({
|
||||
'name': "John Travolta",
|
||||
'iban': "FI37 1590 3000 0007 76",
|
||||
'amount': 12.544
|
||||
})
|
||||
self.assertFalse(form.is_valid())
|
||||
@@ -1,9 +0,0 @@
|
||||
"""Expenses claim urls."""
|
||||
|
||||
from django.conf.urls import url
|
||||
from .views import claim
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^new', claim)
|
||||
]
|
||||
@@ -1,22 +0,0 @@
|
||||
"""Expenses claim views."""
|
||||
|
||||
from django.shortcuts import render
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django.http import HttpResponse
|
||||
from .forms import ExpensesClaim
|
||||
|
||||
|
||||
# Allow only GET or POST
|
||||
@require_http_methods(["GET", "POST"])
|
||||
def claim(request):
|
||||
"""Render expenses claim form."""
|
||||
|
||||
if request.method == 'POST':
|
||||
form = ExpensesClaim(request.POST)
|
||||
if form.is_valid():
|
||||
return HttpResponse()
|
||||
|
||||
elif request.method == 'GET':
|
||||
form = ExpensesClaim()
|
||||
|
||||
return render(request, 'claim.html', {'form': form})
|
||||
+9
-2
@@ -2,8 +2,15 @@
|
||||
|
||||
from django.contrib import admin
|
||||
from infoscreen.models import (
|
||||
Rotation, InfoItem, InfoInstance, ImageInfoItem,
|
||||
ExternalImageInfoItem, ABBInfoItem, ExternalWebsiteInfoItem, VideoInfoItem)
|
||||
Rotation,
|
||||
InfoItem,
|
||||
InfoInstance,
|
||||
ImageInfoItem,
|
||||
ExternalImageInfoItem,
|
||||
ABBInfoItem,
|
||||
ExternalWebsiteInfoItem,
|
||||
VideoInfoItem,
|
||||
)
|
||||
|
||||
# Register your models here.
|
||||
admin.site.register(Rotation)
|
||||
|
||||
+1
-1
@@ -6,4 +6,4 @@ from django.apps import AppConfig
|
||||
class InfoscreenConfig(AppConfig):
|
||||
"""Infoscreen app configuration."""
|
||||
|
||||
name = 'infoscreen'
|
||||
name = "infoscreen"
|
||||
|
||||
@@ -11,81 +11,173 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
("contenttypes", "0002_remove_content_type_name"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='HSLDataModel',
|
||||
name="HSLDataModel",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('data', models.TextField(default='', editable=False)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("data", models.TextField(default="", editable=False)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='InfoInstance',
|
||||
name="InfoInstance",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('duration', models.FloatField(default=15.0)),
|
||||
('item_id', models.PositiveIntegerField()),
|
||||
('item_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("duration", models.FloatField(default=15.0)),
|
||||
("item_id", models.PositiveIntegerField()),
|
||||
(
|
||||
"item_type",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="contenttypes.ContentType",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='InfoItem',
|
||||
name="InfoItem",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('expire_date', models.DateTimeField(blank=True, null=True)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=255)),
|
||||
("expire_date", models.DateTimeField(blank=True, null=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Rotation',
|
||||
name="Rotation",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=255)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ABBInfoItem',
|
||||
name="ABBInfoItem",
|
||||
fields=[
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
],
|
||||
bases=('infoscreen.infoitem',),
|
||||
bases=("infoscreen.infoitem",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ExternalImageInfoItem',
|
||||
name="ExternalImageInfoItem",
|
||||
fields=[
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
('url', models.TextField()),
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
("url", models.TextField()),
|
||||
],
|
||||
bases=('infoscreen.infoitem',),
|
||||
bases=("infoscreen.infoitem",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HslInfoItem',
|
||||
name="HslInfoItem",
|
||||
fields=[
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
],
|
||||
bases=('infoscreen.infoitem',),
|
||||
bases=("infoscreen.infoitem",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ImageInfoItem',
|
||||
name="ImageInfoItem",
|
||||
fields=[
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
('img', models.ImageField(upload_to='infoimages/')),
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
("img", models.ImageField(upload_to="infoimages/")),
|
||||
],
|
||||
bases=('infoscreen.infoitem',),
|
||||
bases=("infoscreen.infoitem",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SossoInfoItem',
|
||||
name="SossoInfoItem",
|
||||
fields=[
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
],
|
||||
bases=('infoscreen.infoitem',),
|
||||
bases=("infoscreen.infoitem",),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='infoinstance',
|
||||
name='rotation',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='instances', to='infoscreen.Rotation'),
|
||||
model_name="infoinstance",
|
||||
name="rotation",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="instances",
|
||||
to="infoscreen.Rotation",
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -9,15 +9,25 @@ import django.db.models.deletion
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('infoscreen', '0001_initial'),
|
||||
("infoscreen", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CoffeeInfoItem',
|
||||
name="CoffeeInfoItem",
|
||||
fields=[
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
],
|
||||
bases=('infoscreen.infoitem',),
|
||||
bases=("infoscreen.infoitem",),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -9,33 +9,63 @@ import django.db.models.deletion
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('infoscreen', '0002_coffeeinfoitem'),
|
||||
("infoscreen", "0002_coffeeinfoitem"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ApyInfoItem',
|
||||
name="ApyInfoItem",
|
||||
fields=[
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
],
|
||||
bases=('infoscreen.infoitem',),
|
||||
bases=("infoscreen.infoitem",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EventInfoItem',
|
||||
name="EventInfoItem",
|
||||
fields=[
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
],
|
||||
bases=('infoscreen.infoitem',),
|
||||
bases=("infoscreen.infoitem",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ExternalWebsiteInfoItem',
|
||||
name="ExternalWebsiteInfoItem",
|
||||
fields=[
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
('url', models.TextField()),
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
("url", models.TextField()),
|
||||
],
|
||||
bases=('infoscreen.infoitem',),
|
||||
bases=("infoscreen.infoitem",),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='CoffeeInfoItem',
|
||||
name="CoffeeInfoItem",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -9,16 +9,26 @@ import django.db.models.deletion
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('infoscreen', '0003_auto_20170329_1857'),
|
||||
("infoscreen", "0003_auto_20170329_1857"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='VideoInfoItem',
|
||||
name="VideoInfoItem",
|
||||
fields=[
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
('video', models.FileField(upload_to='infovideos/')),
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
("video", models.FileField(upload_to="infovideos/")),
|
||||
],
|
||||
bases=('infoscreen.infoitem',),
|
||||
bases=("infoscreen.infoitem",),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -8,18 +8,18 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('infoscreen', '0004_videoinfoitem'),
|
||||
("infoscreen", "0004_videoinfoitem"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='externalimageinfoitem',
|
||||
name='url',
|
||||
model_name="externalimageinfoitem",
|
||||
name="url",
|
||||
field=models.URLField(),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='externalwebsiteinfoitem',
|
||||
name='url',
|
||||
model_name="externalwebsiteinfoitem",
|
||||
name="url",
|
||||
field=models.URLField(),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -8,14 +8,14 @@ from django.db import migrations
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('infoscreen', '0005_auto_20170913_1841'),
|
||||
("infoscreen", "0005_auto_20170913_1841"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='HSLDataModel',
|
||||
name="HSLDataModel",
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='HslInfoItem',
|
||||
name="HslInfoItem",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -7,15 +7,25 @@ import django.db.models.deletion
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('infoscreen', '0006_delete_hsldatamodel'),
|
||||
("infoscreen", "0006_delete_hsldatamodel"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='LunchItem',
|
||||
name="LunchItem",
|
||||
fields=[
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
],
|
||||
bases=('infoscreen.infoitem',),
|
||||
bases=("infoscreen.infoitem",),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 3.2.14 on 2022-08-01 19:13
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("infoscreen", "0007_lunchitem"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="infoinstance",
|
||||
name="id",
|
||||
field=models.AutoField(primary_key=True, serialize=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="infoitem",
|
||||
name="id",
|
||||
field=models.AutoField(primary_key=True, serialize=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="rotation",
|
||||
name="id",
|
||||
field=models.AutoField(primary_key=True, serialize=False),
|
||||
),
|
||||
]
|
||||
+51
-53
@@ -16,6 +16,7 @@ class InfoItem(models.Model):
|
||||
class __meta__:
|
||||
abstract = True
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = models.CharField(max_length=255)
|
||||
# expire_date = None means never expiring item
|
||||
expire_date = models.DateTimeField(blank=True, null=True)
|
||||
@@ -23,14 +24,14 @@ class InfoItem(models.Model):
|
||||
|
||||
def get_template_url(self):
|
||||
"""Get infoscreen template url."""
|
||||
raise NotImplementedError(
|
||||
"inheriting classes must implement get_template_url")
|
||||
raise NotImplementedError("inheriting classes must implement get_template_url")
|
||||
|
||||
@staticmethod
|
||||
def get_create_template_url():
|
||||
"""Get create infoscreen template url command."""
|
||||
raise NotImplementedError(
|
||||
"inheriting classes must implement get_create_template_url")
|
||||
"inheriting classes must implement get_create_template_url"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def create_from_dict(cls, d):
|
||||
@@ -42,14 +43,13 @@ class InfoItem(models.Model):
|
||||
def update_from_dict(self, d):
|
||||
"""Update model based on given dict."""
|
||||
try:
|
||||
expire_date = d.pop('expire_date', None)
|
||||
self.expire_date = datetime.strptime(
|
||||
expire_date, "%Y-%m-%d %H:%M:%S")
|
||||
expire_date = d.pop("expire_date", None)
|
||||
self.expire_date = datetime.strptime(expire_date, "%Y-%m-%d %H:%M:%S")
|
||||
except:
|
||||
pass
|
||||
|
||||
dmap = {
|
||||
'name': 'name',
|
||||
"name": "name",
|
||||
}
|
||||
for k, v in d.items():
|
||||
try:
|
||||
@@ -61,13 +61,13 @@ class InfoItem(models.Model):
|
||||
def get_dict(self):
|
||||
"""Convert django model to dict and return it."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
'item_type': ContentType.objects.get_for_model(self).id,
|
||||
'template_url': self.get_template_url(),
|
||||
'display_name': self.display_name,
|
||||
'create_template_url': self.get_create_template_url(),
|
||||
'options': {}
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"item_type": ContentType.objects.get_for_model(self).id,
|
||||
"template_url": self.get_template_url(),
|
||||
"display_name": self.display_name,
|
||||
"create_template_url": self.get_create_template_url(),
|
||||
"options": {},
|
||||
}
|
||||
|
||||
def delete(self):
|
||||
@@ -75,8 +75,8 @@ class InfoItem(models.Model):
|
||||
# since generic foreign keys suck, delete info
|
||||
# items pointing here manually
|
||||
InfoInstance.objects.filter(
|
||||
item_id=self.id,
|
||||
item_type=ContentType.objects.get_for_model(self)).delete()
|
||||
item_id=self.id, item_type=ContentType.objects.get_for_model(self)
|
||||
).delete()
|
||||
super().delete()
|
||||
|
||||
@classmethod
|
||||
@@ -139,7 +139,7 @@ class ExternalWebsiteInfoItem(InfoItem):
|
||||
def get_dict(self):
|
||||
"""Convert django model to dict and return it."""
|
||||
d = super().get_dict()
|
||||
d["options"] = {'url': self.url}
|
||||
d["options"] = {"url": self.url}
|
||||
return d
|
||||
|
||||
@classmethod
|
||||
@@ -152,23 +152,22 @@ class ExternalWebsiteInfoItem(InfoItem):
|
||||
def get_list(self):
|
||||
"""Return list containing infoitem data."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
'url': self.url,
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"url": self.url,
|
||||
}
|
||||
|
||||
def update_from_dict(self, d):
|
||||
"""Update model based on given dict."""
|
||||
try:
|
||||
expire_date = d.pop('expire_date', None)
|
||||
self.expire_date = datetime.strptime(
|
||||
expire_date, "%Y-%m-%d %H:%M:%S")
|
||||
expire_date = d.pop("expire_date", None)
|
||||
self.expire_date = datetime.strptime(expire_date, "%Y-%m-%d %H:%M:%S")
|
||||
except:
|
||||
pass
|
||||
|
||||
dmap = {
|
||||
'name': 'name',
|
||||
'url': 'url',
|
||||
"name": "name",
|
||||
"url": "url",
|
||||
}
|
||||
for k, v in d.items():
|
||||
try:
|
||||
@@ -241,14 +240,14 @@ class ImageInfoItem(InfoItem):
|
||||
def get_dict(self):
|
||||
"""Convert django model to dict and return it."""
|
||||
d = super().get_dict()
|
||||
d["options"] = {'img': self.img.url}
|
||||
d["options"] = {"img": self.img.url}
|
||||
return d
|
||||
|
||||
|
||||
class VideoInfoItem(InfoItem):
|
||||
"""Class for Video Infoscreen item."""
|
||||
|
||||
display_name = ("Video")
|
||||
display_name = "Video"
|
||||
video = models.FileField(upload_to="infovideos/")
|
||||
|
||||
def get_template_url(self):
|
||||
@@ -263,7 +262,7 @@ class VideoInfoItem(InfoItem):
|
||||
def get_dict(self):
|
||||
"""Convert django model to dict and return it."""
|
||||
d = super().get_dict()
|
||||
d["options"] = {'video': self.video.url}
|
||||
d["options"] = {"video": self.video.url}
|
||||
return d
|
||||
|
||||
|
||||
@@ -285,7 +284,7 @@ class ExternalImageInfoItem(InfoItem):
|
||||
def get_dict(self):
|
||||
"""Convert django model to dict and return it."""
|
||||
d = super().get_dict()
|
||||
d["options"] = {'img': self.url}
|
||||
d["options"] = {"img": self.url}
|
||||
return d
|
||||
|
||||
@classmethod
|
||||
@@ -298,15 +297,14 @@ class ExternalImageInfoItem(InfoItem):
|
||||
def update_from_dict(self, d):
|
||||
"""Update model based on given dict."""
|
||||
try:
|
||||
expire_date = d.pop('expire_date', None)
|
||||
self.expire_date = datetime.strptime(
|
||||
expire_date, "%Y-%m-%d %H:%M:%S")
|
||||
expire_date = d.pop("expire_date", None)
|
||||
self.expire_date = datetime.strptime(expire_date, "%Y-%m-%d %H:%M:%S")
|
||||
except:
|
||||
pass
|
||||
|
||||
dmap = {
|
||||
'name': 'name',
|
||||
'url': 'url',
|
||||
"name": "name",
|
||||
"url": "url",
|
||||
}
|
||||
for k, v in d.items():
|
||||
try:
|
||||
@@ -319,12 +317,15 @@ class ExternalImageInfoItem(InfoItem):
|
||||
class InfoInstance(models.Model):
|
||||
"""Class for Info instance in Infoscreen."""
|
||||
|
||||
rotation = models.ForeignKey('Rotation', related_name='instances', on_delete=models.CASCADE)
|
||||
id = models.AutoField(primary_key=True)
|
||||
rotation = models.ForeignKey(
|
||||
"Rotation", related_name="instances", on_delete=models.CASCADE
|
||||
)
|
||||
duration = models.FloatField(default=15.0) # seconds
|
||||
# generic relation to some kind of InfoItem
|
||||
item_id = models.PositiveIntegerField()
|
||||
item_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||
item = GenericForeignKey('item_type', 'item_id')
|
||||
item = GenericForeignKey("item_type", "item_id")
|
||||
|
||||
@classmethod
|
||||
def create_from_dict(cls, d):
|
||||
@@ -337,31 +338,27 @@ class InfoInstance(models.Model):
|
||||
except:
|
||||
raise RuntimeError("invalid parameters supplied supplied")
|
||||
try:
|
||||
return cls.objects.create(
|
||||
rotation=rotation,
|
||||
item=item,
|
||||
duration=duration
|
||||
)
|
||||
return cls.objects.create(rotation=rotation, item=item, duration=duration)
|
||||
except:
|
||||
raise RuntimeError("error while adding instance to db")
|
||||
|
||||
def get_dict(self):
|
||||
"""Convert django model to dict and return it."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'item': self.item.get_dict(),
|
||||
'duration': self.duration,
|
||||
"id": self.id,
|
||||
"item": self.item.get_dict(),
|
||||
"duration": self.duration,
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
"""Return model name."""
|
||||
return "{}: {} ({}s)".format(
|
||||
self.rotation.name, self.item.name, self.duration)
|
||||
return "{}: {} ({}s)".format(self.rotation.name, self.item.name, self.duration)
|
||||
|
||||
|
||||
class Rotation(models.Model):
|
||||
"""Class for rotation model."""
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = models.CharField(max_length=255)
|
||||
|
||||
def get_dict(self):
|
||||
@@ -370,21 +367,20 @@ class Rotation(models.Model):
|
||||
# to avoid excluding items with no expire_date)
|
||||
now = timezone.now()
|
||||
instances = self.instances.all()
|
||||
filtered = filter(lambda i: (i.item.expire_date or now) >= now,
|
||||
list(instances))
|
||||
filtered = filter(lambda i: (i.item.expire_date or now) >= now, list(instances))
|
||||
instance_list = list(map(lambda i: i.get_dict(), filtered))
|
||||
|
||||
return {
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
'instances': instance_list,
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"instances": instance_list,
|
||||
}
|
||||
|
||||
def get_list(self):
|
||||
"""Return list containing infoitem data."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
@@ -395,6 +391,7 @@ class Rotation(models.Model):
|
||||
class ImageUploadForm(forms.Form):
|
||||
"""Form used to handle imageuploads to infoscreen app."""
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = forms.CharField()
|
||||
image = forms.ImageField()
|
||||
|
||||
@@ -402,5 +399,6 @@ class ImageUploadForm(forms.Form):
|
||||
class UploadFileForm(forms.Form):
|
||||
"""Form used for uploading file."""
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = forms.CharField()
|
||||
video = forms.FileField()
|
||||
|
||||
@@ -3,7 +3,7 @@ body {
|
||||
}
|
||||
|
||||
#header:after {
|
||||
content: " ";
|
||||
content: " ";
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ body {
|
||||
.event {
|
||||
font-size: 100px;
|
||||
font-weight: bold;
|
||||
margin-left: 20px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
.event-col{
|
||||
padding-top:1vh;
|
||||
@@ -21,7 +21,7 @@ body {
|
||||
|
||||
.header-row{
|
||||
margin: 30px;
|
||||
margin-left: 20px;
|
||||
margin-left: 20px;
|
||||
font-size: 130px;
|
||||
padding-bottom:20px;
|
||||
color:#24a05f;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#infocontent {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
z-index: -1; /* Ensure div tag stays behind content; -999 might work, too. */
|
||||
}
|
||||
|
||||
@@ -31,10 +31,10 @@
|
||||
max-height 200px;
|
||||
}
|
||||
|
||||
#sossoimage {
|
||||
#sossoimage {
|
||||
height:300px;
|
||||
position: relative;
|
||||
left: 0px;
|
||||
position: relative;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<link rel="stylesheet" href="/static/infoscreen/css/events.css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Droid+Sans+Mono" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css?family=Droid+Sans+Mono" rel="stylesheet">
|
||||
<div class="container" ng-app="myApp" ng-controller="EventController">
|
||||
<div class="header-row row">
|
||||
<div class="col-sm-6">Tapahtuma</div>
|
||||
|
||||
@@ -56,7 +56,8 @@ app.filter('unsafe', function($sce) {
|
||||
app.controller('ABBController', function($scope, $http){
|
||||
$scope.jobs = [];
|
||||
var min_date = moment().subtract(30,'days').format("YYYY-MM-DD%20HH:mm:ss");
|
||||
var url = "https://sahkoinsinoorikilta.fi/api/news.php";
|
||||
// TODO: FIX, we try to get rid of php, not depend on it!
|
||||
var url = "https://old.sahkoinsinoorikilta.fi/api/news.php";
|
||||
var params = "?type=11&lang=fi&title_search=ABB&min_date="+min_date
|
||||
$http.get(url+params).then(function(response){
|
||||
$scope.jobs = _.filter(response.data, function(job){
|
||||
|
||||
+1
-1
@@ -35,6 +35,6 @@ class InfoscreenTestCase(TestCase):
|
||||
That would mean that something meaningful has been included
|
||||
in the response.
|
||||
"""
|
||||
resp = self.c.get('/infoscreen/items')
|
||||
resp = self.c.get("/infoscreen/items")
|
||||
content = resp.json()
|
||||
self.assertTrue(len(content) > 0)
|
||||
|
||||
+23
-22
@@ -27,30 +27,31 @@ from infoscreen.views import createApyItem
|
||||
from infoscreen.views import get_apy_json
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', default),
|
||||
url(r'^admin$', admin),
|
||||
url(r'^(?P<idx>\d+)$', index),
|
||||
url(r'^items$', info_items),
|
||||
url(r'^rotation/(?P<idx>\d+)$', rotation),
|
||||
url(r'^rotations$', rotations),
|
||||
url(r'^instance$', createInstance),
|
||||
url(r'^instance/(?P<idx>\d+)$', deleteInstance),
|
||||
url(r'^types$', info_types),
|
||||
url(r'^delete_item/(?P<type_id>\d+)/(?P<idx>\d+)$', delete_info_item),
|
||||
url(r'^create_external_image$', createExternalImageInfoItem),
|
||||
url(r'^create_image$', create_image_item),
|
||||
url(r'^create_video$', create_video_item),
|
||||
url(r'^create_abbitem$', createABBItem),
|
||||
url(r'^create_sossoitem$', createSossoItem),
|
||||
url(r'^create_lunchitem$', createLunchItem),
|
||||
url(r'^create_eventitem$', createEventItem),
|
||||
url(r'^create_apyitem$', createApyItem),
|
||||
url(r'^create_websiteitem$', createExternalWebsiteItem),
|
||||
url(r'^create_rotation$', create_rotation),
|
||||
url(r'^delete_rotation/(?P<id>\d+)$', delete_rotation),
|
||||
url(r'^apyjson', get_apy_json),
|
||||
url(r"^$", default),
|
||||
url(r"^admin$", admin),
|
||||
url(r"^(?P<idx>\d+)$", index),
|
||||
url(r"^items$", info_items),
|
||||
url(r"^rotation/(?P<idx>\d+)$", rotation),
|
||||
url(r"^rotations$", rotations),
|
||||
url(r"^instance$", createInstance),
|
||||
url(r"^instance/(?P<idx>\d+)$", deleteInstance),
|
||||
url(r"^types$", info_types),
|
||||
url(r"^delete_item/(?P<type_id>\d+)/(?P<idx>\d+)$", delete_info_item),
|
||||
url(r"^create_external_image$", createExternalImageInfoItem),
|
||||
url(r"^create_image$", create_image_item),
|
||||
url(r"^create_video$", create_video_item),
|
||||
url(r"^create_abbitem$", createABBItem),
|
||||
url(r"^create_sossoitem$", createSossoItem),
|
||||
url(r"^create_lunchitem$", createLunchItem),
|
||||
url(r"^create_eventitem$", createEventItem),
|
||||
url(r"^create_apyitem$", createApyItem),
|
||||
url(r"^create_websiteitem$", createExternalWebsiteItem),
|
||||
url(r"^create_rotation$", create_rotation),
|
||||
url(r"^delete_rotation/(?P<id>\d+)$", delete_rotation),
|
||||
url(r"^apyjson", get_apy_json),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
|
||||
urlpatterns += staticfiles_urlpatterns()
|
||||
|
||||
@@ -16,16 +16,27 @@ import threading
|
||||
import requests
|
||||
|
||||
from infoscreen.models import (
|
||||
Rotation, InfoItem, InfoInstance, ABBInfoItem, ExternalImageInfoItem,
|
||||
ImageInfoItem, SossoInfoItem, LunchItem, EventInfoItem,
|
||||
ExternalWebsiteInfoItem, ImageUploadForm, ApyInfoItem, VideoInfoItem)
|
||||
Rotation,
|
||||
InfoItem,
|
||||
InfoInstance,
|
||||
ABBInfoItem,
|
||||
ExternalImageInfoItem,
|
||||
ImageInfoItem,
|
||||
SossoInfoItem,
|
||||
LunchItem,
|
||||
EventInfoItem,
|
||||
ExternalWebsiteInfoItem,
|
||||
ImageUploadForm,
|
||||
ApyInfoItem,
|
||||
VideoInfoItem,
|
||||
)
|
||||
|
||||
|
||||
@login_required(login_url='/admin/login')
|
||||
@permission_required('infoscreen.change_infoinstance', raise_exception=True)
|
||||
@login_required(login_url="/admin/login")
|
||||
@permission_required("infoscreen.change_infoinstance", raise_exception=True)
|
||||
def admin(request, *args, **kwargs):
|
||||
"""Render infoscreen admin page."""
|
||||
return render(request, 'infoscreen:infoscreen_admin.html', {})
|
||||
return render(request, "infoscreen/infoscreen_admin.html", {})
|
||||
|
||||
|
||||
def create_item_generator(model):
|
||||
@@ -33,20 +44,23 @@ def create_item_generator(model):
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@require_http_methods(["POST"])
|
||||
@login_required(login_url='/admin/login')
|
||||
@permission_required('infoscreen.add_infoinstance', raise_exception=True)
|
||||
@login_required(login_url="/admin/login")
|
||||
@permission_required("infoscreen.add_infoinstance", raise_exception=True)
|
||||
def create_item(request, *args, **kwargs):
|
||||
try:
|
||||
data = json.loads(request.body.decode("utf-8"))
|
||||
except json.JSONDecodeError:
|
||||
return HttpResponseBadRequest(
|
||||
'{"status":"failure","error":"invalid json supplied"}')
|
||||
'{"status":"failure","error":"invalid json supplied"}'
|
||||
)
|
||||
try:
|
||||
model.create_from_dict(data)
|
||||
return HttpResponse('{"status":"success"}')
|
||||
except RuntimeError as e:
|
||||
return HttpResponseBadRequest(
|
||||
json.dumps({"status": "failure", "error": str(e)}))
|
||||
json.dumps({"status": "failure", "error": str(e)})
|
||||
)
|
||||
|
||||
return create_item
|
||||
|
||||
|
||||
@@ -55,8 +69,8 @@ def delete_item_generator(model):
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@require_http_methods(["DELETE"])
|
||||
@login_required(login_url='/admin/login')
|
||||
@permission_required('infoscreen.delete_infoinstance', raise_exception=True)
|
||||
@login_required(login_url="/admin/login")
|
||||
@permission_required("infoscreen.delete_infoinstance", raise_exception=True)
|
||||
def delete_item(request, *args, **kwargs):
|
||||
idx = kwargs.pop("idx", 0)
|
||||
try:
|
||||
@@ -72,13 +86,14 @@ def delete_item_generator(model):
|
||||
resp = HttpResponse('{"error" : "could not delete item"}')
|
||||
resp.status_code = 500
|
||||
return resp
|
||||
|
||||
return delete_item
|
||||
|
||||
|
||||
# due to model structure this is little complicated
|
||||
@ensure_csrf_cookie
|
||||
@login_required(login_url='/admin/login')
|
||||
@permission_required('infoscreen.delete_infoinstance', raise_exception=True)
|
||||
@login_required(login_url="/admin/login")
|
||||
@permission_required("infoscreen.delete_infoinstance", raise_exception=True)
|
||||
@require_http_methods(["DELETE"])
|
||||
def delete_info_item(request, *args, **kwargs):
|
||||
"""Delete info item."""
|
||||
@@ -102,42 +117,44 @@ def delete_info_item(request, *args, **kwargs):
|
||||
|
||||
@require_http_methods(["POST"])
|
||||
@ensure_csrf_cookie
|
||||
@login_required(login_url='/admin/login')
|
||||
@permission_required('infoscreen.add_infoinstance', raise_exception=True)
|
||||
@login_required(login_url="/admin/login")
|
||||
@permission_required("infoscreen.add_infoinstance", raise_exception=True)
|
||||
def create_image_item(request, *args, **kwargs):
|
||||
"""Create image Infoscreen item."""
|
||||
form = ImageUploadForm(request.POST, request.FILES)
|
||||
if not form.is_valid():
|
||||
return HttpResponseBadRequest('{"status": "failure",'
|
||||
'"error": "invalid data supplied"}')
|
||||
return HttpResponseBadRequest(
|
||||
'{"status": "failure",' '"error": "invalid data supplied"}'
|
||||
)
|
||||
|
||||
img = form.cleaned_data['image']
|
||||
name = form.cleaned_data['name']
|
||||
img = form.cleaned_data["image"]
|
||||
name = form.cleaned_data["name"]
|
||||
ImageInfoItem.objects.create(img=img, name=name)
|
||||
return HttpResponse('{"status":"success"}')
|
||||
|
||||
|
||||
@require_http_methods(["POST"])
|
||||
@ensure_csrf_cookie
|
||||
@login_required(login_url='/admin/login')
|
||||
@permission_required('infoscreen.add_infoinstance', raise_exception=True)
|
||||
@login_required(login_url="/admin/login")
|
||||
@permission_required("infoscreen.add_infoinstance", raise_exception=True)
|
||||
def create_video_item(request, *args, **kwargs):
|
||||
"""Create video Infoscreen item."""
|
||||
form = UploadFileForm(request.POST, request.FILES)
|
||||
if not form.is_valid():
|
||||
return HttpResponseBadRequest('{"status": "failure",'
|
||||
'"error": "invalid data supplied"}')
|
||||
return HttpResponseBadRequest(
|
||||
'{"status": "failure",' '"error": "invalid data supplied"}'
|
||||
)
|
||||
|
||||
video = form.cleaned_data['video']
|
||||
name = form.cleaned_data['name']
|
||||
video = form.cleaned_data["video"]
|
||||
name = form.cleaned_data["name"]
|
||||
VideoInfoItem.objects.create(video=video, name=name)
|
||||
return HttpResponse('{"status": "success"}')
|
||||
|
||||
|
||||
@require_http_methods(["POST"])
|
||||
@ensure_csrf_cookie
|
||||
@login_required(login_url='/admin/login')
|
||||
@permission_required('infoscreen.add_rotation', raise_exception=True)
|
||||
@login_required(login_url="/admin/login")
|
||||
@permission_required("infoscreen.add_rotation", raise_exception=True)
|
||||
def create_rotation(request, *args, **kwargs):
|
||||
"""Create rotation."""
|
||||
try:
|
||||
@@ -150,16 +167,15 @@ def create_rotation(request, *args, **kwargs):
|
||||
Rotation.objects.create(name=name)
|
||||
resp = HttpResponse(status=200)
|
||||
except DatabaseError:
|
||||
resp = HttpResponse(
|
||||
'{"error" : "could not create rotation!"}', status=400)
|
||||
resp = HttpResponse('{"error" : "could not create rotation!"}', status=400)
|
||||
|
||||
return resp
|
||||
|
||||
|
||||
@require_http_methods(["DELETE"])
|
||||
@ensure_csrf_cookie
|
||||
@login_required(login_url='/admin/login')
|
||||
@permission_required('infoscreen.delete_rotation', raise_exception=True)
|
||||
@login_required(login_url="/admin/login")
|
||||
@permission_required("infoscreen.delete_rotation", raise_exception=True)
|
||||
def delete_rotation(request, *args, **kwargs):
|
||||
"""Delete rotation."""
|
||||
id = kwargs.pop("id", 0)
|
||||
@@ -169,8 +185,7 @@ def delete_rotation(request, *args, **kwargs):
|
||||
Rotation.objects.filter(id=id).delete()
|
||||
resp = HttpResponse(status=200)
|
||||
except DatabaseError:
|
||||
resp = HttpResponse(
|
||||
'{"error" : "could not delete rotation!"}', status=400)
|
||||
resp = HttpResponse('{"error" : "could not delete rotation!"}', status=400)
|
||||
|
||||
return resp
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import requests
|
||||
@require_http_methods(["GET"])
|
||||
def index(request, idx, *args, **kwargs):
|
||||
"""Render infoscreen index page."""
|
||||
return render(request, 'infoscreen_index.html', {'rotation': idx})
|
||||
return render(request, "infoscreen/infoscreen_index.html", {"rotation": idx})
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
@@ -32,7 +32,8 @@ def default(request, *args, **kwargs):
|
||||
def get_apy_json(request):
|
||||
"""Render APY diilikone page."""
|
||||
return HttpResponse(
|
||||
requests.get("https://api-diilikone.apy.fi/deals/top-groups").text)
|
||||
requests.get("https://api-diilikone.apy.fi/deals/top-groups").text
|
||||
)
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
@@ -61,10 +62,12 @@ def info_types(request, *args, **kwargs):
|
||||
types = []
|
||||
classes = InfoItem.get_subclasses()
|
||||
for c in classes:
|
||||
types.append({
|
||||
"name": c.display_name,
|
||||
"create_template_url": c.get_create_template_url(),
|
||||
})
|
||||
types.append(
|
||||
{
|
||||
"name": c.display_name,
|
||||
"create_template_url": c.get_create_template_url(),
|
||||
}
|
||||
)
|
||||
return HttpResponse(json.dumps(types))
|
||||
|
||||
|
||||
|
||||
+1
-2
@@ -1,10 +1,9 @@
|
||||
from django.contrib import admin
|
||||
from modeltranslation.admin import TranslationAdmin
|
||||
|
||||
from kaehmy.models import Application, Comment, CustomRole, PresetRole, TelegramChannel
|
||||
from kaehmy.models import Application, Comment, CustomRole, PresetRole
|
||||
|
||||
admin.site.register(Application)
|
||||
admin.site.register(Comment)
|
||||
admin.site.register(CustomRole)
|
||||
admin.site.register(PresetRole, TranslationAdmin)
|
||||
admin.site.register(TelegramChannel)
|
||||
|
||||
+1
-1
@@ -2,4 +2,4 @@ from django.apps import AppConfig
|
||||
|
||||
|
||||
class KaehmyConfig(AppConfig):
|
||||
name = 'kaehmy'
|
||||
name = "kaehmy"
|
||||
|
||||
+54
-29
@@ -2,16 +2,20 @@ from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from kaehmy.models import PresetRole, CustomRole, Application, Comment, KaehmyBaseRole
|
||||
from kaehmy.models import PresetRole, CustomRole, Application, Comment, BaseRole
|
||||
|
||||
|
||||
class CheckboxSelectMultiple(forms.widgets.CheckboxSelectMultiple):
|
||||
option_template_name = 'checkbox_option.html'
|
||||
option_template_name = "checkbox_option.html"
|
||||
|
||||
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
|
||||
dic = super(CheckboxSelectMultiple, self).create_option(name, value, label, selected, index, subindex, attrs)
|
||||
description = PresetRole.objects.get(id=value).description
|
||||
dic['description'] = description
|
||||
def create_option(
|
||||
self, name, formIterator, label, selected, index, subindex=None, attrs=None
|
||||
):
|
||||
dic = super(CheckboxSelectMultiple, self).create_option(
|
||||
name, formIterator, label, selected, index, subindex, attrs
|
||||
)
|
||||
description = PresetRole.objects.get(id=formIterator.value).description
|
||||
dic["description"] = description
|
||||
return dic
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -25,30 +29,46 @@ class ApplicationForm(forms.ModelForm):
|
||||
"""Meta for class Application."""
|
||||
|
||||
model = Application
|
||||
fields = ['name', 'email', 'phone_number', 'year',
|
||||
'preset_roles', 'custom_roles', 'custom_role_name',
|
||||
'custom_role_is_board', 'text']
|
||||
fields = [
|
||||
"name",
|
||||
"email",
|
||||
"phone_number",
|
||||
"year",
|
||||
"preset_roles",
|
||||
"custom_roles",
|
||||
"custom_role_name",
|
||||
"custom_role_is_board",
|
||||
"text",
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ApplicationForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.fields["email"].label = _('Email (not public)')
|
||||
self.fields["phone_number"].label = _('Phone number (not public)')
|
||||
self.fields["email"].label = _("Email (not public)")
|
||||
self.fields["phone_number"].label = _("Phone number (not public)")
|
||||
|
||||
custom_roles_exist = CustomRole.objects.all().exists()
|
||||
self.fields["custom_roles"].widget = forms.widgets.CheckboxSelectMultiple() if custom_roles_exist else forms.HiddenInput()
|
||||
self.fields["custom_roles"].widget = (
|
||||
forms.widgets.CheckboxSelectMultiple()
|
||||
if custom_roles_exist
|
||||
else forms.HiddenInput()
|
||||
)
|
||||
self.fields["custom_roles"].help_text = ""
|
||||
self.fields["custom_roles"].label = _('Custom roles')
|
||||
self.fields["custom_roles"].label = _("Custom roles")
|
||||
self.fields["custom_roles"].queryset = CustomRole.objects.all()
|
||||
|
||||
for cat_id, category in KaehmyBaseRole.CATEGORIES:
|
||||
key = 'preset_roles_{}'.format(cat_id)
|
||||
qset = PresetRole.objects.filter(category=cat_id).order_by('category', '-is_board')
|
||||
for cat_id, category in BaseRole.CATEGORIES:
|
||||
key = "preset_roles_{}".format(cat_id)
|
||||
qset = PresetRole.objects.filter(category=cat_id).order_by(
|
||||
"category", "-is_board"
|
||||
)
|
||||
self.fields[key] = forms.ModelMultipleChoiceField(qset)
|
||||
self.fields[key].widget = CheckboxSelectMultiple(attrs={
|
||||
'title': _('Preset roles'),
|
||||
'name': 'preset_roles',
|
||||
})
|
||||
self.fields[key].widget = CheckboxSelectMultiple(
|
||||
attrs={
|
||||
"title": _("Preset roles"),
|
||||
"name": "preset_roles",
|
||||
}
|
||||
)
|
||||
self.fields[key].help_text = ""
|
||||
self.fields[key].queryset = qset
|
||||
self.fields[key].label = _(category)
|
||||
@@ -57,33 +77,38 @@ class ApplicationForm(forms.ModelForm):
|
||||
def clean(self):
|
||||
cleaned_data = super(ApplicationForm, self).clean()
|
||||
for key in cleaned_data.keys():
|
||||
if 'preset_roles_' in key:
|
||||
cleaned_data['preset_roles'] = cleaned_data['preset_roles'] | cleaned_data[key]
|
||||
if "preset_roles_" in key:
|
||||
cleaned_data["preset_roles"] = (
|
||||
cleaned_data["preset_roles"] | cleaned_data[key]
|
||||
)
|
||||
|
||||
return cleaned_data
|
||||
|
||||
def clean_phone_number(self):
|
||||
"""Clean phone number field."""
|
||||
number = self.cleaned_data.get('phone_number')
|
||||
number = self.cleaned_data.get("phone_number")
|
||||
if number.isdigit():
|
||||
return number
|
||||
else:
|
||||
raise ValidationError(_('Invalid phone number'))
|
||||
raise ValidationError(_("Invalid phone number"))
|
||||
|
||||
def clean_custom_role_name(self):
|
||||
"""Check that no other custom role with same name exists."""
|
||||
custom_name = self.cleaned_data.get('custom_role_name')
|
||||
custom_name = self.cleaned_data.get("custom_role_name")
|
||||
if not CustomRole.objects.filter(name=custom_name).exists():
|
||||
return custom_name
|
||||
else:
|
||||
raise ValidationError(_('Custom role with the same name already exists.'))
|
||||
raise ValidationError(_("Custom role with the same name already exists."))
|
||||
|
||||
def non_role_fields(self):
|
||||
return [self.fields[k] for k in self.fields.keys() if k not in ["preset_roles", "custom_roles"]]
|
||||
return [
|
||||
self.fields[k]
|
||||
for k in self.fields.keys()
|
||||
if k not in ["preset_roles", "custom_roles"]
|
||||
]
|
||||
|
||||
|
||||
class CommentForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Comment
|
||||
fields = ['name', 'email', 'message', 'parent']
|
||||
fields = ["name", "email", "message", "parent"]
|
||||
|
||||
@@ -12,82 +12,188 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('webapp', '0037_auto_20180125_2131'),
|
||||
("webapp", "0037_auto_20180125_2131"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CommentParent',
|
||||
name="CommentParent",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(default='', max_length=255, verbose_name='Name')),
|
||||
('email', models.EmailField(default='', max_length=254, verbose_name='Email')),
|
||||
('timestamp', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Timestamp')),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"name",
|
||||
models.CharField(default="", max_length=255, verbose_name="Name"),
|
||||
),
|
||||
(
|
||||
"email",
|
||||
models.EmailField(default="", max_length=254, verbose_name="Email"),
|
||||
),
|
||||
(
|
||||
"timestamp",
|
||||
models.DateTimeField(
|
||||
default=django.utils.timezone.now, verbose_name="Timestamp"
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CustomRole',
|
||||
name="CustomRole",
|
||||
fields=[
|
||||
('baserole_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='webapp.BaseRole')),
|
||||
(
|
||||
"baserole_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="webapp.BaseRole",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Custom kaehmy roles',
|
||||
'verbose_name': 'Custom kaehmy role',
|
||||
"verbose_name_plural": "Custom kaehmy roles",
|
||||
"verbose_name": "Custom kaehmy role",
|
||||
},
|
||||
bases=('webapp.baserole',),
|
||||
bases=("webapp.baserole",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PresetRole',
|
||||
name="PresetRole",
|
||||
fields=[
|
||||
('presetrole_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='webapp.PresetRole')),
|
||||
(
|
||||
"presetrole_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="webapp.PresetRole",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Preset kaehmy roles',
|
||||
'verbose_name': 'Preset kaehmy role',
|
||||
"verbose_name_plural": "Preset kaehmy roles",
|
||||
"verbose_name": "Preset kaehmy role",
|
||||
},
|
||||
bases=('webapp.presetrole',),
|
||||
bases=("webapp.presetrole",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TelegramChannel',
|
||||
name="TelegramChannel",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('channel_id', models.CharField(max_length=255, unique=True)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=255)),
|
||||
("channel_id", models.CharField(max_length=255, unique=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Telegram channels',
|
||||
'verbose_name': 'Telegram channel',
|
||||
"verbose_name_plural": "Telegram channels",
|
||||
"verbose_name": "Telegram channel",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Application',
|
||||
name="Application",
|
||||
fields=[
|
||||
('commentparent_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='kaehmy.CommentParent')),
|
||||
('phone_number', models.CharField(default='', max_length=10, verbose_name='Phone number')),
|
||||
('year', models.IntegerField(choices=[(1, '1'), (2, '2'), (3, '3'), (4, '4'), (5, 'N')], verbose_name='Year')),
|
||||
('text', models.TextField(default='', max_length=300, verbose_name='Text')),
|
||||
('custom_role_name', models.CharField(blank=True, max_length=255, verbose_name='Custom role name')),
|
||||
('custom_role_is_board', models.BooleanField(verbose_name='Board member')),
|
||||
('custom_roles', models.ManyToManyField(blank=True, related_name='forms', to='kaehmy.CustomRole')),
|
||||
('preset_roles', models.ManyToManyField(blank=True, related_name='forms', to='kaehmy.PresetRole')),
|
||||
(
|
||||
"commentparent_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="kaehmy.CommentParent",
|
||||
),
|
||||
),
|
||||
(
|
||||
"phone_number",
|
||||
models.CharField(
|
||||
default="", max_length=10, verbose_name="Phone number"
|
||||
),
|
||||
),
|
||||
(
|
||||
"year",
|
||||
models.IntegerField(
|
||||
choices=[(1, "1"), (2, "2"), (3, "3"), (4, "4"), (5, "N")],
|
||||
verbose_name="Year",
|
||||
),
|
||||
),
|
||||
(
|
||||
"text",
|
||||
models.TextField(default="", max_length=300, verbose_name="Text"),
|
||||
),
|
||||
(
|
||||
"custom_role_name",
|
||||
models.CharField(
|
||||
blank=True, max_length=255, verbose_name="Custom role name"
|
||||
),
|
||||
),
|
||||
(
|
||||
"custom_role_is_board",
|
||||
models.BooleanField(verbose_name="Board member"),
|
||||
),
|
||||
(
|
||||
"custom_roles",
|
||||
models.ManyToManyField(
|
||||
blank=True, related_name="forms", to="kaehmy.CustomRole"
|
||||
),
|
||||
),
|
||||
(
|
||||
"preset_roles",
|
||||
models.ManyToManyField(
|
||||
blank=True, related_name="forms", to="kaehmy.PresetRole"
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Kaehmylomakkeet',
|
||||
'verbose_name': 'Kaehmylomake',
|
||||
"verbose_name_plural": "Kaehmylomakkeet",
|
||||
"verbose_name": "Kaehmylomake",
|
||||
},
|
||||
bases=('kaehmy.commentparent',),
|
||||
bases=("kaehmy.commentparent",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Comment',
|
||||
name="Comment",
|
||||
fields=[
|
||||
('commentparent_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='kaehmy.CommentParent')),
|
||||
('message', models.TextField(verbose_name='Message')),
|
||||
('parent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='kaehmy.CommentParent')),
|
||||
(
|
||||
"commentparent_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="kaehmy.CommentParent",
|
||||
),
|
||||
),
|
||||
("message", models.TextField(verbose_name="Message")),
|
||||
(
|
||||
"parent",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="messages",
|
||||
to="kaehmy.CommentParent",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Kaehmykommentit',
|
||||
'verbose_name': 'Kaehmykommentti',
|
||||
"verbose_name_plural": "Kaehmykommentit",
|
||||
"verbose_name": "Kaehmykommentti",
|
||||
},
|
||||
bases=('kaehmy.commentparent',),
|
||||
bases=("kaehmy.commentparent",),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -7,26 +7,59 @@ import django.db.models.deletion
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('webapp', '0047_auto_20180710_2110'),
|
||||
('kaehmy', '0001_initial'),
|
||||
("webapp", "0047_auto_20180710_2110"),
|
||||
("kaehmy", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='KaehmyBaseRole',
|
||||
name="KaehmyBaseRole",
|
||||
fields=[
|
||||
('baserole_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='webapp.BaseRole')),
|
||||
('category', models.CharField(choices=[('corporate', 'Corporate affairs'), ('freshman', 'Freshmen'), ('international', 'International'), ('external', 'External affairs'), ('media', 'Media'), ('tech', 'Technology'), ('wellbeing', 'Wellbeing'), ('elepaja', 'Elepaja'), ('ceremonies', 'Ceremonies'), ('culture', 'Culture'), ('studies', 'Studies'), ('sosso', 'Sössö magazine'), ('alumni', 'Alumni relations'), ('others', 'Others')], default='others', max_length=255, verbose_name='Category')),
|
||||
(
|
||||
"baserole_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="webapp.BaseRole",
|
||||
),
|
||||
),
|
||||
(
|
||||
"category",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("corporate", "Corporate affairs"),
|
||||
("freshman", "Freshmen"),
|
||||
("international", "International"),
|
||||
("external", "External affairs"),
|
||||
("media", "Media"),
|
||||
("tech", "Technology"),
|
||||
("wellbeing", "Wellbeing"),
|
||||
("elepaja", "Elepaja"),
|
||||
("ceremonies", "Ceremonies"),
|
||||
("culture", "Culture"),
|
||||
("studies", "Studies"),
|
||||
("sosso", "Sössö magazine"),
|
||||
("alumni", "Alumni relations"),
|
||||
("others", "Others"),
|
||||
],
|
||||
default="others",
|
||||
max_length=255,
|
||||
verbose_name="Category",
|
||||
),
|
||||
),
|
||||
],
|
||||
bases=('webapp.baserole',),
|
||||
bases=("webapp.baserole",),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Application',
|
||||
name="Application",
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='customrole',
|
||||
name="customrole",
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='presetrole',
|
||||
name="presetrole",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -7,57 +7,113 @@ import django.db.models.deletion
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('kaehmy', '0002_auto_20180902_1929'),
|
||||
("kaehmy", "0002_auto_20180902_1929"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Application',
|
||||
name="Application",
|
||||
fields=[
|
||||
('commentparent_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='kaehmy.CommentParent')),
|
||||
('phone_number', models.CharField(default='', max_length=10, verbose_name='Phone number')),
|
||||
('year', models.IntegerField(choices=[(1, '1'), (2, '2'), (3, '3'), (4, '4'), (5, 'N')], verbose_name='Year')),
|
||||
('text', models.TextField(default='', max_length=300, verbose_name='Text')),
|
||||
('custom_role_name', models.CharField(blank=True, max_length=255, verbose_name='Custom role name')),
|
||||
('custom_role_is_board', models.BooleanField(verbose_name='Board member')),
|
||||
(
|
||||
"commentparent_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="kaehmy.CommentParent",
|
||||
),
|
||||
),
|
||||
(
|
||||
"phone_number",
|
||||
models.CharField(
|
||||
default="", max_length=10, verbose_name="Phone number"
|
||||
),
|
||||
),
|
||||
(
|
||||
"year",
|
||||
models.IntegerField(
|
||||
choices=[(1, "1"), (2, "2"), (3, "3"), (4, "4"), (5, "N")],
|
||||
verbose_name="Year",
|
||||
),
|
||||
),
|
||||
(
|
||||
"text",
|
||||
models.TextField(default="", max_length=300, verbose_name="Text"),
|
||||
),
|
||||
(
|
||||
"custom_role_name",
|
||||
models.CharField(
|
||||
blank=True, max_length=255, verbose_name="Custom role name"
|
||||
),
|
||||
),
|
||||
(
|
||||
"custom_role_is_board",
|
||||
models.BooleanField(verbose_name="Board member"),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Kaehmylomake',
|
||||
'verbose_name_plural': 'Kaehmylomakkeet',
|
||||
"verbose_name": "Kaehmylomake",
|
||||
"verbose_name_plural": "Kaehmylomakkeet",
|
||||
},
|
||||
bases=('kaehmy.commentparent',),
|
||||
bases=("kaehmy.commentparent",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CustomRole',
|
||||
name="CustomRole",
|
||||
fields=[
|
||||
('kaehmybaserole_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='kaehmy.KaehmyBaseRole')),
|
||||
(
|
||||
"kaehmybaserole_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="kaehmy.KaehmyBaseRole",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Custom kaehmy role',
|
||||
'verbose_name_plural': 'Custom kaehmy roles',
|
||||
"verbose_name": "Custom kaehmy role",
|
||||
"verbose_name_plural": "Custom kaehmy roles",
|
||||
},
|
||||
bases=('kaehmy.kaehmybaserole',),
|
||||
bases=("kaehmy.kaehmybaserole",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PresetRole',
|
||||
name="PresetRole",
|
||||
fields=[
|
||||
('kaehmybaserole_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='kaehmy.KaehmyBaseRole')),
|
||||
('description', models.TextField(verbose_name='Description')),
|
||||
(
|
||||
"kaehmybaserole_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="kaehmy.KaehmyBaseRole",
|
||||
),
|
||||
),
|
||||
("description", models.TextField(verbose_name="Description")),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Preset kaehmy role',
|
||||
'verbose_name_plural': 'Preset kaehmy roles',
|
||||
"verbose_name": "Preset kaehmy role",
|
||||
"verbose_name_plural": "Preset kaehmy roles",
|
||||
},
|
||||
bases=('kaehmy.kaehmybaserole',),
|
||||
bases=("kaehmy.kaehmybaserole",),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='application',
|
||||
name='custom_roles',
|
||||
field=models.ManyToManyField(blank=True, related_name='forms', to='kaehmy.CustomRole'),
|
||||
model_name="application",
|
||||
name="custom_roles",
|
||||
field=models.ManyToManyField(
|
||||
blank=True, related_name="forms", to="kaehmy.CustomRole"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='application',
|
||||
name='preset_roles',
|
||||
field=models.ManyToManyField(blank=True, related_name='forms', to='kaehmy.PresetRole'),
|
||||
model_name="application",
|
||||
name="preset_roles",
|
||||
field=models.ManyToManyField(
|
||||
blank=True, related_name="forms", to="kaehmy.PresetRole"
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -6,13 +6,32 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('kaehmy', '0003_auto_20180902_1943'),
|
||||
("kaehmy", "0003_auto_20180902_1943"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='kaehmybaserole',
|
||||
name='category',
|
||||
field=models.CharField(choices=[('corporate', 'Corporate affairs'), ('freshman', 'Freshmen'), ('international', 'International'), ('external', 'External affairs'), ('media', 'Media'), ('tech', 'Technology'), ('wellbeing', 'Wellbeing'), ('elepaja', 'Elepaja'), ('ceremonies', 'Ceremonies'), ('studies', 'Studies'), ('sosso', 'Sössö magazine'), ('alumni', 'Alumni relations'), ('others', 'Others')], default='others', max_length=255, verbose_name='Category'),
|
||||
model_name="kaehmybaserole",
|
||||
name="category",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("corporate", "Corporate affairs"),
|
||||
("freshman", "Freshmen"),
|
||||
("international", "International"),
|
||||
("external", "External affairs"),
|
||||
("media", "Media"),
|
||||
("tech", "Technology"),
|
||||
("wellbeing", "Wellbeing"),
|
||||
("elepaja", "Elepaja"),
|
||||
("ceremonies", "Ceremonies"),
|
||||
("studies", "Studies"),
|
||||
("sosso", "Sössö magazine"),
|
||||
("alumni", "Alumni relations"),
|
||||
("others", "Others"),
|
||||
],
|
||||
default="others",
|
||||
max_length=255,
|
||||
verbose_name="Category",
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('kaehmy', '0004_auto_20181018_2121'),
|
||||
("kaehmy", "0004_auto_20181018_2121"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='application',
|
||||
name='custom_role_is_board',
|
||||
field=models.BooleanField(blank=True, verbose_name='Board member'),
|
||||
model_name="application",
|
||||
name="custom_role_is_board",
|
||||
field=models.BooleanField(blank=True, verbose_name="Board member"),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# Generated by Django 2.2.26 on 2022-01-12 20:38
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("kaehmy", "0005_auto_20190312_1458"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name="TelegramChannel",
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.14 on 2022-08-01 19:13
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("kaehmy", "0006_delete_telegramchannel"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="commentparent",
|
||||
name="id",
|
||||
field=models.AutoField(primary_key=True, serialize=False),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,44 @@
|
||||
# Generated by Django 3.2.14 on 2022-08-03 20:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("kaehmy", "0007_alter_commentparent_id"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="BaseRole",
|
||||
fields=[
|
||||
("id", models.AutoField(primary_key=True, serialize=False)),
|
||||
("name", models.CharField(max_length=255, verbose_name="Name")),
|
||||
("is_board", models.BooleanField(verbose_name="Board member")),
|
||||
(
|
||||
"category",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("corporate", "Corporate affairs"),
|
||||
("freshman", "Freshmen"),
|
||||
("international", "International"),
|
||||
("external", "External affairs"),
|
||||
("media", "Media"),
|
||||
("tech", "Technology"),
|
||||
("wellbeing", "Wellbeing"),
|
||||
("elepaja", "Elepaja"),
|
||||
("ceremonies", "Ceremonies"),
|
||||
("studies", "Studies"),
|
||||
("sosso", "Sössö magazine"),
|
||||
("alumni", "Alumni relations"),
|
||||
("others", "Others"),
|
||||
],
|
||||
default="others",
|
||||
max_length=255,
|
||||
verbose_name="Category",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,29 @@
|
||||
# Generated by Django 2.2.28 on 2022-07-26 17:15
|
||||
|
||||
from unicodedata import category
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def copyBaseRolesToNewTable(apps, schema_editor):
|
||||
Old = apps.get_model("kaehmy", "KaehmyBaseRole")
|
||||
New = apps.get_model("kaehmy", "BaseRole")
|
||||
for bases in Old.objects.all():
|
||||
New.objects.create(
|
||||
id=bases.id,
|
||||
name=bases.name,
|
||||
is_board=bases.is_board,
|
||||
category=bases.category,
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("kaehmy", "0008_baserole"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
copyBaseRolesToNewTable, reverse_code=migrations.RunPython.noop
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,51 @@
|
||||
# Generated by Django 2.2.28 on 2022-07-26 17:33
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
from sikweb.custom_operations import AlterModelBases
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("kaehmy", "0009_auto_20220726_2015"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
AlterModelBases("customrole", (models.Model,)),
|
||||
AlterModelBases("presetrole", (models.Model,)),
|
||||
migrations.AlterField(
|
||||
model_name="customrole",
|
||||
name="kaehmybaserole_ptr",
|
||||
field=models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="BaseRole",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="presetrole",
|
||||
name="kaehmybaserole_ptr",
|
||||
field=models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="BaseRole",
|
||||
),
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="customrole",
|
||||
old_name="kaehmybaserole_ptr",
|
||||
new_name="baserole_ptr",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="presetrole",
|
||||
old_name="kaehmybaserole_ptr",
|
||||
new_name="baserole_ptr",
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,16 @@
|
||||
# Generated by Django 3.2.14 on 2022-08-03 20:19
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("kaehmy", "0010_auto_20220726_2033"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name="KaehmyBaseRole",
|
||||
),
|
||||
]
|
||||
+81
-77
@@ -1,64 +1,71 @@
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
from webapp.utils import month_from_now
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from auditlog.registry import auditlog
|
||||
from phonenumber_field.modelfields import PhoneNumberField
|
||||
|
||||
import logging
|
||||
|
||||
import webapp.models
|
||||
|
||||
VERBOSE_NAME = _('Kaehmy')
|
||||
VERBOSE_NAME = _("Kaehmy")
|
||||
|
||||
|
||||
class KaehmyBaseRole(webapp.models.BaseRole):
|
||||
class BaseRole(models.Model):
|
||||
"""Base model for occupations/roles."""
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = models.CharField(_("Name"), max_length=255)
|
||||
is_board = models.BooleanField(_("Board member"))
|
||||
|
||||
CATEGORIES = (
|
||||
('corporate', _('Corporate affairs')),
|
||||
('freshman', _('Freshmen')),
|
||||
('international', _('International')),
|
||||
('external', _('External affairs')),
|
||||
('media', _('Media')),
|
||||
('tech', _('Technology')),
|
||||
('wellbeing', _('Wellbeing')),
|
||||
('elepaja', _('Elepaja')),
|
||||
('ceremonies', _('Ceremonies')),
|
||||
('studies', _('Studies')),
|
||||
('sosso', _('Sössö magazine')),
|
||||
('alumni', _('Alumni relations')),
|
||||
('others', _('Others')),
|
||||
("board", _("Board")),
|
||||
("corporate", _("Corporate affairs")),
|
||||
("freshman", _("Freshmen")),
|
||||
("international", _("International")),
|
||||
("external", _("External affairs")),
|
||||
("media", _("Media")),
|
||||
("tech", _("Technology")),
|
||||
("wellbeing", _("Wellbeing")),
|
||||
("sikpaja", _("Sik-paja")),
|
||||
("ceremonies", _("Ceremonies")),
|
||||
("studies", _("Studies")),
|
||||
("sosso", _("Sössö magazine")),
|
||||
("pota", _("PoTa")),
|
||||
("alumni", _("Alumni relations")),
|
||||
("n", _("N")),
|
||||
("others", _("Others")),
|
||||
)
|
||||
category = models.CharField(_('Category'), choices=CATEGORIES, default='others', max_length=255)
|
||||
category = models.CharField(
|
||||
_("Category"), choices=CATEGORIES, default="others", max_length=255
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
n = self.name.capitalize()
|
||||
return "{} ({})".format(n, _("board member")) if self.is_board else n
|
||||
|
||||
|
||||
class PresetRole(KaehmyBaseRole):
|
||||
class PresetRole(BaseRole):
|
||||
"""Model for kaehmy role."""
|
||||
|
||||
description = models.TextField(_('Description'))
|
||||
description = models.TextField(_("Description"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Preset kaehmy role')
|
||||
verbose_name_plural = _('Preset kaehmy roles')
|
||||
verbose_name = _("Preset kaehmy role")
|
||||
verbose_name_plural = _("Preset kaehmy roles")
|
||||
|
||||
|
||||
class CustomRole(KaehmyBaseRole):
|
||||
class CustomRole(BaseRole):
|
||||
"""Model representing a user-specified custom occupation."""
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Custom kaehmy role')
|
||||
verbose_name_plural = _('Custom kaehmy roles')
|
||||
verbose_name = _("Custom kaehmy role")
|
||||
verbose_name_plural = _("Custom kaehmy roles")
|
||||
|
||||
|
||||
class CommentParent(models.Model):
|
||||
|
||||
name = models.CharField(_('Name'), max_length=255, default='')
|
||||
email = models.EmailField(_('Email'), default='')
|
||||
timestamp = models.DateTimeField(_('Timestamp'), default=timezone.now)
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = models.CharField(_("Name"), max_length=255, default="")
|
||||
email = models.EmailField(_("Email"), default="")
|
||||
timestamp = models.DateTimeField(_("Timestamp"), default=timezone.now)
|
||||
|
||||
def __str__(self):
|
||||
return 'Message parent #{}'.format(self.id)
|
||||
return "Message parent #{}".format(self.id)
|
||||
|
||||
|
||||
class Comment(CommentParent):
|
||||
@@ -69,11 +76,13 @@ class Comment(CommentParent):
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Kaehmykommentti')
|
||||
verbose_name_plural = _('Kaehmykommentit')
|
||||
verbose_name = _("Kaehmykommentti")
|
||||
verbose_name_plural = _("Kaehmykommentit")
|
||||
|
||||
message = models.TextField(_('Message'))
|
||||
parent = models.ForeignKey('CommentParent', related_name='messages', on_delete=models.CASCADE)
|
||||
message = models.TextField(_("Message"))
|
||||
parent = models.ForeignKey(
|
||||
"CommentParent", related_name="messages", on_delete=models.CASCADE
|
||||
)
|
||||
|
||||
|
||||
class Application(CommentParent):
|
||||
@@ -82,34 +91,36 @@ class Application(CommentParent):
|
||||
|
||||
Allows user to choose from existing roles or to create custom ones.
|
||||
"""
|
||||
|
||||
YEAR_CHOICES = (
|
||||
(1, '1'),
|
||||
(2, '2'),
|
||||
(3, '3'),
|
||||
(4, '4'),
|
||||
(5, 'N'),
|
||||
(1, "1"),
|
||||
(2, "2"),
|
||||
(3, "3"),
|
||||
(4, "4"),
|
||||
(5, "N"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Kaehmylomake')
|
||||
verbose_name_plural = _('Kaehmylomakkeet')
|
||||
verbose_name = _("Kaehmylomake")
|
||||
verbose_name_plural = _("Kaehmylomakkeet")
|
||||
|
||||
phone_number = models.CharField(
|
||||
_('Phone number'), max_length=10, default="")
|
||||
year = models.IntegerField(_('Year'), choices=YEAR_CHOICES)
|
||||
text = models.TextField(_('Text'), default="", max_length=300)
|
||||
phone_number = models.CharField(_("Phone number"), max_length=10, default="")
|
||||
year = models.IntegerField(_("Year"), choices=YEAR_CHOICES)
|
||||
text = models.TextField(_("Text"), default="", max_length=300)
|
||||
custom_role_name = models.CharField(
|
||||
_('Custom role name'), max_length=255, blank=True)
|
||||
custom_role_is_board = models.BooleanField(
|
||||
_('Board member'), blank=True)
|
||||
_("Custom role name"), max_length=255, blank=True
|
||||
)
|
||||
custom_role_is_board = models.BooleanField(_("Board member"), blank=True)
|
||||
custom_roles = models.ManyToManyField(
|
||||
'kaehmy.CustomRole', related_name='forms', blank=True)
|
||||
"kaehmy.CustomRole", related_name="forms", blank=True
|
||||
)
|
||||
preset_roles = models.ManyToManyField(
|
||||
'kaehmy.PresetRole', related_name='forms', blank=True)
|
||||
"kaehmy.PresetRole", related_name="forms", blank=True
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
"""Return model info."""
|
||||
return _('Kaehmy application: {}').format(self.name)
|
||||
return _("Kaehmy application: {}").format(self.name)
|
||||
|
||||
def comment_count(self):
|
||||
"""Count comments for kaehmy."""
|
||||
@@ -131,34 +142,27 @@ class Application(CommentParent):
|
||||
presets = [r.name.capitalize() for r in self.preset_roles.filter(is_board=True)]
|
||||
customs = [r.name.capitalize() for r in self.custom_roles.filter(is_board=True)]
|
||||
combined = presets + customs
|
||||
return _('Board: {}').format(', '.join(combined)) if len(combined) > 0 else ''
|
||||
return _("Board: {}").format(", ".join(combined)) if len(combined) > 0 else ""
|
||||
|
||||
def official_roles(self):
|
||||
presets = [r.name.capitalize() for r in self.preset_roles.filter(is_board=False)]
|
||||
customs = [r.name.capitalize() for r in self.custom_roles.filter(is_board=False)]
|
||||
presets = [
|
||||
r.name.capitalize() for r in self.preset_roles.filter(is_board=False)
|
||||
]
|
||||
customs = [
|
||||
r.name.capitalize() for r in self.custom_roles.filter(is_board=False)
|
||||
]
|
||||
combined = presets + customs
|
||||
return _('Official: {}').format(', '.join(combined)) if len(combined) > 0 else ''
|
||||
return (
|
||||
_("Official: {}").format(", ".join(combined)) if len(combined) > 0 else ""
|
||||
)
|
||||
|
||||
def all_roles(self):
|
||||
presets = [r.name.capitalize() for r in self.preset_roles.all()]
|
||||
customs = [r.name.capitalize() for r in self.custom_roles.all()]
|
||||
combined = presets + customs
|
||||
return ', '.join(combined) if len(combined) > 0 else ''
|
||||
return ", ".join(combined) if len(combined) > 0 else ""
|
||||
|
||||
def has_any_board_role(self):
|
||||
return self.preset_roles.filter(is_board=True).exists() or self.custom_roles.filter(is_board=True)
|
||||
|
||||
|
||||
# Telegram channel entry for Kaehmys
|
||||
class TelegramChannel(models.Model):
|
||||
"""Model containing the channel id of a Telegram chat"""
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Telegram channel')
|
||||
verbose_name_plural = _('Telegram channels')
|
||||
|
||||
name = models.CharField(max_length=255)
|
||||
channel_id = models.CharField(max_length=255, unique=True)
|
||||
|
||||
def __str__(self):
|
||||
return 'Telegram channel: "{}"'.format(self.name)
|
||||
return self.preset_roles.filter(
|
||||
is_board=True
|
||||
).exists() or self.custom_roles.filter(is_board=True)
|
||||
|
||||
@@ -5,12 +5,6 @@
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
body {
|
||||
max-width: 1000px;
|
||||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
}
|
||||
|
||||
div.tooltip-inner {
|
||||
max-width: 25rem;
|
||||
}
|
||||
@@ -28,6 +22,10 @@ div.tooltip-inner {
|
||||
.kaehmy-content {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
max-width: 1000px;
|
||||
width: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
p {
|
||||
|
||||
@@ -3,13 +3,9 @@
|
||||
}
|
||||
|
||||
footer {
|
||||
/* position: absolute; */
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 60px; /* Set the fixed height of the footer here */
|
||||
/* line-height: 60px; /* Vertically center the text there */
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
footer .container .col .nav .nav-item {
|
||||
@@ -26,6 +22,7 @@ footer .container .col .nav .nav-item {
|
||||
|
||||
.lang-select {
|
||||
width: 10rem;
|
||||
margin-bottom: 1rem;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +1,28 @@
|
||||
.header-content {
|
||||
|
||||
.kaehmy-header {
|
||||
background-color: #0c2938;
|
||||
}
|
||||
|
||||
.header-content .logo {
|
||||
.kaehmy-header-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
}
|
||||
|
||||
.header-content .logo img {
|
||||
display: block;
|
||||
height: auto;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.kaehmy-banner {
|
||||
max-width: 1000px;
|
||||
width: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1000px) {
|
||||
.kaehmy_header-content {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background-color: #052f5f;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.kaehmy_header {
|
||||
margin-bottom: 331px;
|
||||
}
|
||||
}
|
||||
|
||||
.kaehmy-banner-image {
|
||||
width: 100%;
|
||||
max-height: 10rem;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.heading {
|
||||
display: flex;
|
||||
place-content: center;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
margin: 1rem;
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
.kaehmy_navigation {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.navbar-border {
|
||||
border-bottom: 2px solid #282b3b;
|
||||
}
|
||||
|
||||
.navbar-light .navbar-nav .nav-link {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
max-width: 1000px;
|
||||
width: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 88 KiB |
+8
-2
@@ -8,6 +8,12 @@ from kaehmy.models import Application
|
||||
class ExportTable(tables.Table):
|
||||
class Meta:
|
||||
model = Application
|
||||
exclude = ['text', 'messageparent_ptr', 'custom_role_name', 'custom_role_is_board', 'timestamp']
|
||||
exclude = [
|
||||
"text",
|
||||
"messageparent_ptr",
|
||||
"custom_role_name",
|
||||
"custom_role_is_board",
|
||||
"timestamp",
|
||||
]
|
||||
|
||||
all_roles = tables.Column(verbose_name=_('Roles'), orderable=False)
|
||||
all_roles = tables.Column(verbose_name=_("Roles"), orderable=False)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{% if wrap_label %}
|
||||
<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>{% endif %}
|
||||
<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>
|
||||
{% include "django/forms/widgets/input.html" %}
|
||||
{% if wrap_label %} {{ widget.label }}</label>{% endif %}
|
||||
{{ widget.label }}</label>
|
||||
<span class="fa fa-info-circle" data-toggle="tooltip" data-placement="right" title="{{ widget.description }}"></span>
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load staticfiles %}
|
||||
|
||||
<footer style="text-align: center">
|
||||
<div>
|
||||
<form class="lang-form form" action="{% url 'set_language' %}" method="post">{% csrf_token %}
|
||||
<span>
|
||||
<input name="next" type="hidden" value="{{ redirect_to }}" />
|
||||
<select onchange="this.form.submit()" class="lang-select form-control" name="language">
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
{% get_available_languages as LANGUAGES %}
|
||||
{% get_language_info_list for LANGUAGES as languages %}
|
||||
{% for language in languages %}
|
||||
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
|
||||
{{ language.name_local }} ({{ language.code }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</span>
|
||||
</form>
|
||||
<span>{% trans "Copyright Aalto-yliopiston Sähköinsinöörikilta ry" %} {% now 'Y' %}</span>
|
||||
</div>
|
||||
</footer>
|
||||
@@ -1,7 +0,0 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div class="kaehmy_header-content">
|
||||
<div class="kaehmy-banner logo">
|
||||
<a href="/kaehmy"><img class="kaehmy-banner-image" src="/static/img/kaehmy_banner.png" alt="Aalto-yliopiston Sähköinsinöörikilta ry"></a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,38 +0,0 @@
|
||||
'''
|
||||
A telegram bot api for whatever purposes.
|
||||
TODO: kaehmy app is definitely not correct place for this
|
||||
'''
|
||||
import logging
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from kaehmy.models import TelegramChannel
|
||||
|
||||
|
||||
class TelegramBot:
|
||||
'''
|
||||
A telegram bot api for whatever purposes
|
||||
Currently only able to broadcast stuff to all registered
|
||||
channels using broadcast method.
|
||||
'''
|
||||
|
||||
def __init__(self, api_token=None):
|
||||
|
||||
self.api_token = api_token or settings.TELEGRAM_BOT_TOKEN
|
||||
self.send_message_url = "https://api.telegram.org/bot{}/sendMessage".format(self.api_token)
|
||||
|
||||
def broadcast(self, message):
|
||||
channels_ids = TelegramChannel.objects.values_list("channel_id", flat=True)
|
||||
for id_ in channels_ids:
|
||||
self.send_message(id_, message)
|
||||
|
||||
def send_message(self, channel_id, message):
|
||||
'''
|
||||
Send message to a chat with given channel_id
|
||||
'''
|
||||
data = {
|
||||
'chat_id': channel_id,
|
||||
'text': message,
|
||||
'parse_mode': 'Markdown'
|
||||
}
|
||||
resp = requests.post(self.send_message_url, json=data)
|
||||
logging.debug(resp.content)
|
||||
@@ -6,13 +6,13 @@ from kaehmy.models import PresetRole, CustomRole
|
||||
|
||||
@register(PresetRole)
|
||||
class PresetRoleTranslationOptions(TranslationOptions):
|
||||
""" Class for PresetRole translation options"""
|
||||
"""Class for PresetRole translation options"""
|
||||
|
||||
fields = ()
|
||||
|
||||
|
||||
@register(CustomRole)
|
||||
class CustomRoleTranslationOptions(TranslationOptions):
|
||||
""" Class for CustomROle translation options"""
|
||||
"""Class for CustomROle translation options"""
|
||||
|
||||
fields = ()
|
||||
|
||||
+7
-6
@@ -13,14 +13,15 @@ from kaehmy.views import export_view
|
||||
|
||||
urlpatterns = [
|
||||
# kaehmy
|
||||
url(r'^new', view),
|
||||
url(r'^submit', submit),
|
||||
url(r'^add_comment', comment),
|
||||
url(r'^statistics', statistics_view),
|
||||
url(r'^export', export_view),
|
||||
url(r'^$', list_view),
|
||||
url(r"^new", view),
|
||||
url(r"^submit", submit),
|
||||
url(r"^add_comment", comment),
|
||||
url(r"^statistics", statistics_view),
|
||||
url(r"^export", export_view),
|
||||
url(r"^$", list_view),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
|
||||
urlpatterns += staticfiles_urlpatterns()
|
||||
|
||||
+74
-78
@@ -1,22 +1,20 @@
|
||||
from django.db.models import Count
|
||||
from django.shortcuts import render, redirect
|
||||
from django.contrib.auth import login, logout, authenticate
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.contrib.auth.decorators import permission_required, login_required
|
||||
from django.conf import settings
|
||||
from django.core.mail import send_mail
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
import logging
|
||||
import requests
|
||||
from dealer.git import git
|
||||
from sikweb.settings import URL
|
||||
|
||||
from members.views.utils import *
|
||||
from kaehmy.models import Application, CustomRole, PresetRole, TelegramChannel
|
||||
from kaehmy.models import Application, CustomRole, PresetRole
|
||||
from kaehmy.forms import ApplicationForm, CommentForm
|
||||
from kaehmy.tables import ExportTable
|
||||
from webapp.utils import send_email
|
||||
from webapp.models import processHooks
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@@ -24,28 +22,38 @@ from webapp.utils import send_email
|
||||
def list_view(request, *args, **kwargs):
|
||||
"""Kaehmy application list"""
|
||||
|
||||
role_filter = request.GET.get('role', None)
|
||||
if role_filter is not None and str(role_filter) != '-1':
|
||||
applications = Application.objects.filter(custom_roles__id=role_filter) | Application.objects.filter(preset_roles__id=role_filter)
|
||||
role_filter = request.GET.get("role", None)
|
||||
if role_filter is not None and str(role_filter) != "-1":
|
||||
applications = Application.objects.filter(
|
||||
custom_roles__id=role_filter
|
||||
) | Application.objects.filter(preset_roles__id=role_filter)
|
||||
else:
|
||||
applications = Application.objects.all()
|
||||
|
||||
applications = applications.order_by('-timestamp')
|
||||
filter_options_preset = PresetRole.objects.annotate(form_count=Count('forms')).filter(form_count__gt=0)
|
||||
filter_options_preset_list = [(r.id, r.name, r.form_count) for r in filter_options_preset]
|
||||
filter_options_custom = CustomRole.objects.annotate(form_count=Count('forms')).filter(form_count__gt=0)
|
||||
filter_options_custom_list = [(r.id, r.name, r.form_count) for r in filter_options_custom]
|
||||
applications = applications.order_by("-timestamp")
|
||||
filter_options_preset = PresetRole.objects.annotate(
|
||||
form_count=Count("forms")
|
||||
).filter(form_count__gt=0)
|
||||
filter_options_preset_list = [
|
||||
(r.id, r.name, r.form_count) for r in filter_options_preset
|
||||
]
|
||||
filter_options_custom = CustomRole.objects.annotate(
|
||||
form_count=Count("forms")
|
||||
).filter(form_count__gt=0)
|
||||
filter_options_custom_list = [
|
||||
(r.id, r.name, r.form_count) for r in filter_options_custom
|
||||
]
|
||||
|
||||
filter_options = filter_options_preset_list + filter_options_custom_list
|
||||
filter_options.sort(key=lambda f: f[1])
|
||||
|
||||
context = {
|
||||
'applications': applications,
|
||||
'application_count': len(applications),
|
||||
'filter_options': filter_options
|
||||
"applications": applications,
|
||||
"application_count": len(applications),
|
||||
"filter_options": filter_options,
|
||||
}
|
||||
|
||||
return render(request, 'kaehmy:list.html', context)
|
||||
return render(request, "kaehmy/list.html", context)
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@@ -56,23 +64,22 @@ def comment(request, *args, **kwargs):
|
||||
form = CommentForm(request.POST)
|
||||
if form.is_valid():
|
||||
comment = form.save()
|
||||
email = comment.parent.email
|
||||
name = comment.name
|
||||
url = f"https://{URL}/kaehmy"
|
||||
|
||||
subject = 'Kaehmyysi tai kommenttiisi on vastattu!'
|
||||
body = ('{} on vastannut kaehmyhakemukseesi tai kommenttiisi kaehmypalvelussa.\r\n\r\n'
|
||||
'Käy lukemassa viesti osoitteessa http://sika.sahkoinsinoorikilta.fi/kaehmy').format(name.capitalize())
|
||||
to_email = comment.parent.email
|
||||
subject = "Kaehmyysi tai kommenttiisi on vastattu!"
|
||||
message = render_to_string(
|
||||
"kaehmy/email_comment.html", {"name": name, "url": url}
|
||||
)
|
||||
|
||||
send_email(email, subject, body)
|
||||
logging.debug(
|
||||
'Sent kaehmy comment email to recipient <{}>'.format(email))
|
||||
send_email(to=to_email, subject=subject, body=message, html=True)
|
||||
logging.debug(f"Sent kaehmy comment email to recipient <{to_email}>")
|
||||
|
||||
return redirect('/kaehmy')
|
||||
return redirect("/kaehmy")
|
||||
else:
|
||||
context = {
|
||||
'error': form.errors
|
||||
}
|
||||
return render(request, 'kaehmy:error.html', context)
|
||||
context = {"error": form.errors}
|
||||
return render(request, "kaehmy/error.html", context)
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
@@ -87,24 +94,24 @@ def statistics_view(request, *args, **kwargs):
|
||||
|
||||
for preset in preset_roles:
|
||||
people = [form.name for form in preset.forms.all()]
|
||||
role_list.append((preset.name, len(people), ', '.join(people)))
|
||||
role_list.append((preset.name, len(people), ", ".join(people)))
|
||||
for custom in custom_roles:
|
||||
people = [form.name for form in custom.forms.all()]
|
||||
role_list.append((custom.name, len(people), ', '.join(people)))
|
||||
role_list.append((custom.name, len(people), ", ".join(people)))
|
||||
|
||||
context = {
|
||||
'applications': applications,
|
||||
'application_count': len(applications),
|
||||
'role_list': role_list
|
||||
"applications": applications,
|
||||
"application_count": len(applications),
|
||||
"role_list": role_list,
|
||||
}
|
||||
return render(request, 'kaehmy:statistics.html', context)
|
||||
return render(request, "kaehmy/statistics.html", context)
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
def view(request, *args, **kwargs):
|
||||
"""Render Kaehmy form page."""
|
||||
form = ApplicationForm()
|
||||
return render(request, 'kaehmy:kaehmy.html', {'form': form})
|
||||
return render(request, "kaehmy/kaehmy.html", {"form": form})
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@@ -114,56 +121,45 @@ def submit(request, *args, **kwargs):
|
||||
form = ApplicationForm(request.POST)
|
||||
if form.is_valid():
|
||||
application = form.save()
|
||||
custom_name = form.cleaned_data.get('custom_role_name')
|
||||
custom_is_board = form.cleaned_data.get('custom_role_is_board')
|
||||
custom_name = form.cleaned_data.get("custom_role_name")
|
||||
custom_is_board = form.cleaned_data.get("custom_role_is_board")
|
||||
|
||||
if len(custom_name) > 0:
|
||||
custom_role = CustomRole(
|
||||
name=custom_name, is_board=custom_is_board)
|
||||
custom_role = CustomRole(name=custom_name, is_board=custom_is_board)
|
||||
custom_role.save()
|
||||
application.custom_roles.add(custom_role)
|
||||
|
||||
url = 'https://sika.sahkoinsinoorikilta.fi/kaehmy'
|
||||
url = f"https://{URL}/kaehmy"
|
||||
name = form.cleaned_data.get("name", "Anonymous")
|
||||
|
||||
email = form.cleaned_data.get('email', '')
|
||||
name = form.cleaned_data.get('name', 'Anonymous')
|
||||
subject = 'Arwokas kirjattu kirje mahdolliselle tulewalle kiltahenkilölle'
|
||||
body = ('Moikka {}!\r\n\r\nHienoa, että kilta kiinnostaa! Kaehmysi on vastaanotettu.\r\n'
|
||||
'Mahdollisista kommenteista tulee ilmoitus sähköpostitse.\r\n\r\n'
|
||||
'Käy katsomassa kaehmytilanne osoitteessa {}').format(name, url)
|
||||
to_email = form.cleaned_data.get("email", "")
|
||||
subject = "Arwokas kirjattu kirje mahdolliselle tulewalle kiltahenkilölle"
|
||||
message = render_to_string(
|
||||
"kaehmy/email_kaehmy.html", {"name": name, "url": url}
|
||||
)
|
||||
|
||||
send_email(email, subject, body)
|
||||
logging.debug('Sent kaehmy email to recipient <{}>'.format(email))
|
||||
|
||||
CHAT_IDS = [channel.channel_id for channel in TelegramChannel.objects.all()]
|
||||
for chat_id in CHAT_IDS:
|
||||
tg_string = 'https://api.telegram.org/bot{}/sendMessage?chat_id={}&text={}'.format(
|
||||
settings.TELEGRAM_BOT_TOKEN,
|
||||
chat_id,
|
||||
'Uusi New kaehmy! {} -> {}'.format(name, url)
|
||||
)
|
||||
response = requests.get(tg_string).json()
|
||||
logging.debug('Telegram API response:\n{}'.format(response))
|
||||
logging.debug('Sent kaehmy announcement to {} channels.'.format(len(CHAT_IDS)))
|
||||
send_email(to=to_email, subject=subject, body=message, html=True)
|
||||
logging.debug(f"Sent kaehmy email to recipient <{to_email}>")
|
||||
|
||||
processHooks(message=f"Uusi New kaehmy! {name} -> {url}", eventType="kaehmy")
|
||||
else:
|
||||
context = {
|
||||
'error': form.errors
|
||||
}
|
||||
return render(request, 'kaehmy:error.html', context)
|
||||
return HttpResponseRedirect('/kaehmy')
|
||||
context = {"error": form.errors}
|
||||
return render(request, "kaehmy/error.html", context)
|
||||
return HttpResponseRedirect("/kaehmy")
|
||||
|
||||
|
||||
@require_http_methods(['GET'])
|
||||
@login_required(login_url='/admin/login')
|
||||
@require_http_methods(["GET"])
|
||||
@login_required(login_url="/admin/login")
|
||||
def export_view(request, *args, **kwargs):
|
||||
def make_table(queryset):
|
||||
table = ExportTable(queryset,
|
||||
request=request,
|
||||
exclude=['id'],
|
||||
attrs={'class': 'table table-bordered table-hover'})
|
||||
table = ExportTable(
|
||||
queryset,
|
||||
request=request,
|
||||
exclude=["id"],
|
||||
attrs={"class": "table table-bordered table-hover"},
|
||||
)
|
||||
|
||||
table.paginate(page=request.GET.get('page', 1), per_page=9999)
|
||||
table.paginate(page=request.GET.get("page", 1), per_page=9999)
|
||||
table_html = convert_table_to_html(table, request)
|
||||
return table_html
|
||||
|
||||
@@ -172,7 +168,7 @@ def export_view(request, *args, **kwargs):
|
||||
board = filter(lambda q: q.has_any_board_role(), kaehmys)
|
||||
|
||||
context = {
|
||||
'non_board_table': make_table(non_board),
|
||||
'board_table': make_table(board),
|
||||
"non_board_table": make_table(non_board),
|
||||
"board_table": make_table(board),
|
||||
}
|
||||
return render(request, 'kaehmy:export.html', context)
|
||||
return render(request, "kaehmy/export.html", context)
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
# Linux/Mac installation instructions
|
||||
|
||||
## Install dependencies
|
||||
|
||||
### Dependency list
|
||||
|
||||
* Python >3.5
|
||||
* PostgreSQL >9.5
|
||||
* pip3
|
||||
* virtualenv
|
||||
* npm
|
||||
|
||||
Install with apt:
|
||||
|
||||
```bash
|
||||
sudo apt install python3
|
||||
sudo apt install python3-pip
|
||||
sudo apt install postgresql
|
||||
sudo pip3 install virtualenv
|
||||
sudo apt install npm
|
||||
```
|
||||
|
||||
More info about PostgreSQL at:
|
||||
[https://www.postgresql.org/](https://www.postgresql.org)
|
||||
|
||||
These packages might be needed on certain platforms:
|
||||
|
||||
* python3-dev
|
||||
* libffi-dev
|
||||
* python3-cffi
|
||||
* libssl-dev
|
||||
|
||||
## Create a virtual environment for python
|
||||
|
||||
Create a virtualenv in the parent directory.
|
||||
|
||||
```bash
|
||||
virtualenv -p python3 ../virtualenv.sikweb
|
||||
```
|
||||
|
||||
## Activate virtualenv
|
||||
|
||||
Assuming we are at the root of this repository and virtualenv is one level above.
|
||||
|
||||
```bash
|
||||
. ../virtualenv.sikweb/bin/activate
|
||||
```
|
||||
|
||||
## Run install wizard
|
||||
|
||||
Run the install wizard with
|
||||
|
||||
```bash
|
||||
bash setup.sh
|
||||
```
|
||||
|
||||
and follow the instructions.
|
||||
|
||||
## Done
|
||||
|
||||
## In case of error on macOS Mojave 10.14
|
||||
|
||||
If you get an error saying
|
||||
|
||||
```bash
|
||||
The headers or library files could not be found for zlib,
|
||||
a required dependency when compiling Pillow from source.
|
||||
```
|
||||
|
||||
run
|
||||
|
||||
```bash
|
||||
xcode-select --install
|
||||
sudo installer -pkg /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg -target /
|
||||
```
|
||||
Binary file not shown.
+848
-786
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+757
-747
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -8,4 +8,4 @@ admin.site.register(Member)
|
||||
admin.site.register(Request)
|
||||
admin.site.register(Payment)
|
||||
|
||||
admin.site.site_header = 'SIK Admin'
|
||||
admin.site.site_header = "SIK Admin"
|
||||
|
||||
+1
-1
@@ -6,4 +6,4 @@ from django.apps import AppConfig
|
||||
class MembersConfig(AppConfig):
|
||||
"""Class for Members app configurations."""
|
||||
|
||||
name = 'members'
|
||||
name = "members"
|
||||
|
||||
+25
-22
@@ -23,7 +23,7 @@ class MemberForm(forms.ModelForm):
|
||||
"""Meta for Member model form."""
|
||||
|
||||
model = Member
|
||||
fields = ['first_name', 'last_name', 'email', 'POR', 'AYY', 'jas']
|
||||
fields = ["first_name", "last_name", "email", "POR", "AYY", "jas"]
|
||||
|
||||
class ImportResult:
|
||||
def __init__(self, members, payments):
|
||||
@@ -32,22 +32,27 @@ class MemberForm(forms.ModelForm):
|
||||
|
||||
def _clean_boolean_field(self, key):
|
||||
value = self.data.get(key, None)
|
||||
if value in ['1', '0']:
|
||||
if value in ["1", "0"]:
|
||||
return bool(int(value))
|
||||
else:
|
||||
return value == 'on'
|
||||
return value == "on"
|
||||
|
||||
def clean_jas(self):
|
||||
return self._clean_boolean_field('jas')
|
||||
return self._clean_boolean_field("jas")
|
||||
|
||||
def clean_AYY(self):
|
||||
return self._clean_boolean_field('AYY')
|
||||
return self._clean_boolean_field("AYY")
|
||||
|
||||
@staticmethod
|
||||
def csv_to_models(data, payment_source='AYY', delimiter=','):
|
||||
clean_data = data.strip().split('\n')
|
||||
clean_data = [row.rstrip(',').rstrip('\r').strip() for row in clean_data]
|
||||
csv_reader = csv.DictReader(clean_data, fieldnames=MemberForm.Meta.fields, delimiter=delimiter, quoting=csv.QUOTE_NONE)
|
||||
def csv_to_models(data, payment_source="AYY", delimiter=","):
|
||||
clean_data = data.strip().split("\n")
|
||||
clean_data = [row.rstrip(",").rstrip("\r").strip() for row in clean_data]
|
||||
csv_reader = csv.DictReader(
|
||||
clean_data,
|
||||
fieldnames=MemberForm.Meta.fields,
|
||||
delimiter=delimiter,
|
||||
quoting=csv.QUOTE_NONE,
|
||||
)
|
||||
|
||||
members = []
|
||||
payments = []
|
||||
@@ -57,10 +62,10 @@ class MemberForm(forms.ModelForm):
|
||||
line[key] = value.strip()
|
||||
except AttributeError as ex:
|
||||
logging.error('Invalid line in CSV: "{}"'.format(line))
|
||||
logging.error('Delimiter: {}'.format(delimiter))
|
||||
logging.error("Delimiter: {}".format(delimiter))
|
||||
raise
|
||||
|
||||
email = line['email']
|
||||
email = line["email"]
|
||||
member_exists = False
|
||||
if Member.objects.filter(email=email).exists():
|
||||
member_exists = True
|
||||
@@ -76,9 +81,9 @@ class MemberForm(forms.ModelForm):
|
||||
else:
|
||||
member = Member.objects.get(email=email)
|
||||
payment_data = {
|
||||
'source': payment_source,
|
||||
'member': member.id,
|
||||
'date': timezone.now(),
|
||||
"source": payment_source,
|
||||
"member": member.id,
|
||||
"date": timezone.now(),
|
||||
}
|
||||
form = PaymentForm(payment_data)
|
||||
if not form.is_valid():
|
||||
@@ -95,17 +100,15 @@ class PaymentForm(forms.ModelForm):
|
||||
|
||||
member = forms.ModelChoiceField(
|
||||
queryset=Member.objects.all(),
|
||||
widget=autocomplete.ModelSelect2(url='member-autocomplete')
|
||||
widget=autocomplete.ModelSelect2(url="member-autocomplete"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
"""Meta for Payment model form."""
|
||||
|
||||
model = Payment
|
||||
fields = ['date', 'source', 'member']
|
||||
labels = {
|
||||
'member': _('Member')
|
||||
}
|
||||
fields = ["date", "source", "member"]
|
||||
labels = {"member": _("Member")}
|
||||
|
||||
|
||||
class ApplicationForm(forms.ModelForm):
|
||||
@@ -115,13 +118,13 @@ class ApplicationForm(forms.ModelForm):
|
||||
"""Meta for application model form."""
|
||||
|
||||
model = Request
|
||||
fields = ['first_name', 'last_name', 'email', 'AYY', 'jas', 'POR']
|
||||
fields = ["first_name", "last_name", "email", "AYY", "jas", "POR"]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ApplicationForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.fields['AYY'].label = _("I'm a member of AYY")
|
||||
self.fields['jas'].label = _("I want to receive a weekly newsletter")
|
||||
self.fields["AYY"].label = _("I'm a member of AYY")
|
||||
self.fields["jas"].label = _("I want to receive a weekly newsletter")
|
||||
|
||||
|
||||
class UploadFileForm(forms.Form):
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.contrib.auth.models import User, Permission
|
||||
from rest_framework.authtoken.models import Token
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
user_name = "sahkopiikki"
|
||||
password = User.objects.make_random_password()
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if User.objects.filter(username=self.user_name).exists():
|
||||
self.stdout.write("Sahkopiikki user already exists. Skipping.")
|
||||
user = User.objects.get(username=self.user_name)
|
||||
token = Token.objects.get(user=user)
|
||||
self.stdout.write("Token: {}".format(token))
|
||||
return
|
||||
|
||||
u = User(username=self.user_name)
|
||||
u.set_password(self.password)
|
||||
|
||||
u.save()
|
||||
|
||||
permission = Permission.objects.get(codename='check_by_email')
|
||||
u.user_permissions.add(permission)
|
||||
|
||||
token = Token.objects.create(user=u)
|
||||
|
||||
self.stdout.write("Created sahkopiikki user '{}' with password '{}' and token '{}'.".format(
|
||||
self.user_name, self.password, token
|
||||
))
|
||||
@@ -12,29 +12,52 @@ class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Member',
|
||||
name="Member",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('first_name', models.CharField(max_length=127)),
|
||||
('last_name', models.CharField(max_length=127)),
|
||||
('email', models.EmailField(max_length=254)),
|
||||
('POR', models.CharField(max_length=255)),
|
||||
('AYY', models.BooleanField(default=False)),
|
||||
('jas', models.BooleanField(default=False)),
|
||||
('created', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('paid', models.DateTimeField(default=datetime.datetime(1970, 1, 1, 0, 0))),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("first_name", models.CharField(max_length=127)),
|
||||
("last_name", models.CharField(max_length=127)),
|
||||
("email", models.EmailField(max_length=254)),
|
||||
("POR", models.CharField(max_length=255)),
|
||||
("AYY", models.BooleanField(default=False)),
|
||||
("jas", models.BooleanField(default=False)),
|
||||
("created", models.DateTimeField(default=django.utils.timezone.now)),
|
||||
(
|
||||
"paid",
|
||||
models.DateTimeField(default=datetime.datetime(1970, 1, 1, 0, 0)),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MemberRequest',
|
||||
name="MemberRequest",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('member', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='members.Member')),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"member",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to="members.Member"
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
@@ -9,13 +9,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('members', '0001_initial'),
|
||||
("members", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='member',
|
||||
name='paid',
|
||||
model_name="member",
|
||||
name="paid",
|
||||
field=models.DateTimeField(default=datetime.datetime(1970, 1, 1, 2, 0)),
|
||||
),
|
||||
]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user