11 Commits

Author SHA1 Message Date
Aarni Halinen e83b4d4624 Add translations to password reset 2017-10-31 22:34:27 +02:00
Aarni Halinen 38225cabc8 Write password recovery emails in html 2017-10-31 22:34:27 +02:00
henu 08bb63ce1f Add margin to footer 2017-10-31 22:27:51 +02:00
henu 72a93e1dfd Merge branch 'develop' of sika.sahkoinsinoorikilta.fi:vtmk/web2.0 into feature-refactor-webapp-styles 2017-10-31 22:24:37 +02:00
henu fa5597f7cf Add centering back to navigation 2017-10-31 22:24:15 +02:00
henu 92ea427c53 Add copyright icon to footer 2017-10-31 22:23:36 +02:00
henu abc2519bc7 Add collapse property for navbar 2017-10-31 22:18:30 +02:00
henu 87a0c68ef2 Update meta tag 2017-10-31 22:17:44 +02:00
henu 612b17960a Make fa-icons larger and add padding between them 2017-10-31 22:16:52 +02:00
Aarni Halinen 7f21b7bba2 Add bootstrap4 to password recovery html 2017-10-31 21:37:19 +02:00
henu 8c116d58de Remove unnecessary comments 2017-10-31 21:17:36 +02:00
300 changed files with 3618 additions and 11812 deletions
-2
View File
@@ -1,5 +1,3 @@
[report]
show_missing = True
[run]
omit =
*/migrations/*
-16
View File
@@ -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
-1
View File
@@ -3,4 +3,3 @@ infoscreen/static/js/lib
webapp/static/js/lib
static/js/lib
collected_static
venv
+2 -5
View File
@@ -5,6 +5,7 @@ sikweb/settings.py
*.sqlite3
uwsgi.ini
uwsgi.log
infoscreen/static/js/hsl.json
members/logs/*
.idea/
logs/
@@ -16,8 +17,4 @@ requirements_henu.txt
/collected_static/
mydatabase
settings.json
.vscode/
.DS_Store
*.code-workspace
sik_test
venv/
.vscode/
+9 -11
View File
@@ -31,7 +31,7 @@ pycodestyle:
- pycodestyle --config=setup.cfg --count .
eslint:
image: node:alpine
image: node:7.10.0
stage: lint
before_script:
- npm install
@@ -39,7 +39,7 @@ eslint:
- npm run eslint
remark:
image: node:alpine
image: node:7.10.0
stage: lint
before_script:
- npm install
@@ -48,14 +48,13 @@ remark:
publish:
stage: publish
image: docker:stable
services:
- docker:stable-dind
image: docker:latest
only:
- develop
script:
before_script:
- docker info
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $REGISTRY_URL
script:
- docker build . -t "$IMAGE_NAME"
- docker push "$IMAGE_NAME"
@@ -64,7 +63,7 @@ deploy_dev:
image: alpine:latest
environment:
name: dev
url: http://web.sik.party:8000
url: http://web.sik.party:8080
only:
- develop
before_script:
@@ -77,9 +76,7 @@ deploy_dev:
- '[[ -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\""
- ssh $DEV_SSH_USER@$DEV_SSH_HOST "cd deployment && docker-compose down && docker pull \"$IMAGE_NAME\" && docker-compose up -d && docker image prune -f"
deploy_production:
stage: deploy
@@ -100,3 +97,4 @@ deploy_production:
- echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
script:
- ssh $PROD_SSH_USER@$PROD_SSH_HOST "zsh ~/deploy.sh"
-1
View File
@@ -1 +0,0 @@
3.6.8
+1 -1
View File
@@ -1,4 +1,4 @@
FROM python:3.5
FROM python:3
ENV PYTHONUNBUFFERED 1
ENV IS_DOCKER 1
RUN mkdir /code
-52
View File
@@ -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
View File
@@ -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"
}
}
}
+23
View File
@@ -0,0 +1,23 @@
from django.apps import AppConfig
import logging
import sys
from coffee_scale import mqtt
class CoffeeScaleConfig(AppConfig):
name = 'coffee_scale'
def ready(self):
if ('makemigrations' in sys.argv or 'migrate' in sys.argv):
return
try:
logging.info('Connecting to MQTT (coffee scale) at {}...'.format(mqtt.HOST))
logging.info('If there is no confirmation, the MQTT connection has probably failed.')
mqtt.client.connect_async(mqtt.HOST, mqtt.PORT, 60)
mqtt.client.loop_start()
except Exception as ex:
logging.error(ex)
logging.error('Failed to connect to MQTT at {}'.format(mqtt.HOST))
+58
View File
@@ -0,0 +1,58 @@
import paho.mqtt.client as mqtt
import logging
import datetime
from collections import deque
from django.conf import settings
HOST = settings.MQTT_SETTINGS['HOST']
PORT = settings.MQTT_SETTINGS['PORT']
TOPICS = settings.MQTT_SETTINGS['TOPICS']
latest = {}
def on_connect(client, userdata, flags, rc):
logging.info('Connected successfully to MQTT.')
logging.info('Subscribing to all topics on {}.'.format(HOST))
client.subscribe('sik/kiltahuone/kahvivaaka/#')
def update_latest(msg):
payload = msg.payload.decode('utf-8')
if msg.topic == TOPICS['WEIGHT']:
weight = float(payload)
latest['weight'] = weight
elif msg.topic == TOPICS['CUPS']:
cups = float(payload)
latest['cups'] = cups
elif msg.topic == TOPICS['BREWING']:
brewing = bool(int(payload))
latest['brewing'] = brewing
elif msg.topic == TOPICS['BREW_TIME']:
brew_time = datetime.datetime.fromtimestamp(float(payload))
latest['brew_time'] = brew_time
def on_message(client, userdata, msg):
try:
update_latest(msg)
except Exception as ex:
logging.exception('Failed to parse MQTT payload.')
def on_disconnect(client, userdata, rc):
if rc != 0:
logging.warning('MQTT unexpectedly disconnected.')
else:
client.loop_stop(force=False)
logging.warning('MQTT disconnected.')
def get_latest():
return latest
client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.on_disconnect = on_disconnect
@@ -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);
@@ -16,7 +16,7 @@ body {
background-size: contain;
background-repeat: no-repeat;
background-position: bottom center;
background-image: url("/static/coffee_scale/img/smokes.png");
background-image: url("/static/img/smokes.png");
transform-origin: bottom;
animation: smokes 8s ease-in-out 0s infinite;
opacity:0;
@@ -27,7 +27,7 @@ body {
background-size: contain;
background-repeat: no-repeat;
background-position: top center;
background-image: url("/static/coffee_scale/img/coffeecup3.png");
background-image: url("/static/img/coffeecup3.png");
height:60%;
}
#scale{
@@ -47,7 +47,7 @@ body {
background: green;
border-radius: 10px;
}
.brewtime{
#brewtime{
text-align:right;
position:absolute;
right:0px;
@@ -62,13 +62,10 @@ body {
font-size:4vw;
color: #333;
}
.layertwo{
display: None;
}
noscript{
color:red;
}
.text{
#text{
color:green;
position:absolute;
top:50%;
@@ -105,9 +102,9 @@ noscript{
}
@keyframes coffeeready {
0% {background-color:white;}
25% {background-color:rgb(100, 255, 100);}
25% {background-color:green;}
50% {background-color:white;}
75% {background-color:rgb(100, 255, 100);}
75% {background-color:green;}
100% {background-color:white;}
}
@keyframes unknown {

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

+130
View File
@@ -0,0 +1,130 @@
var len = 0;
var lastBrew = "&infin;";
var brewtext = "";
$(document).ready(function(){
$('#text').bind("DOMSubtreeModified", resize);
updateTime();
setInterval(updateTime,1000);
formatBrewTime();
setInterval(formatBrewTime,10000);
});
$(window).resize(resize);
function fetchdata(data, status){
if (typeof status !== 'undefined'){
if (status == "success"){
parseData(data);
}
else if (status == "error"){
handleError();
}
}
}
$.getJSON("/coffee/cups", fetchdata);
setInterval(function() {
$.getJSON("/coffee/cups", fetchdata);
}, 2000);
function formatBrewTime(){
if (!brewtext && lastBrew instanceof Date){
var now = new Date();
var timeDiff = Math.max(now.getTime() - lastBrew.getTime(), 0);
var tmp = (timeDiff < 3600000)
? Math.round(timeDiff / 60000) + ' min'
: '~' + Math.round(timeDiff / 3600000 * 2) / 2 + ' h';
$("#brewtime").html(tmp);
} else {
$("#brewtime").html(brewtext);
}
}
function handleError() {
setData("?", 0, 0, Number.MAX_VALUE, Number.MAX_VALUE);
}
function parseData(data) {
if (data) {
var date = new Date(data.date);
lastBrew = new Date(data.last_brew);
var now = new Date();
var cups = data.cups;
var brewing = data.brewing;
var timeDiff = Math.max(now.getTime() - lastBrew.getTime(),0);
var opa = Math.max(100 - timeDiff / 90000,0);
setData(cups, data.temp, opa,now.getTime()-date.getTime(), timeDiff, brewing);
}
else{
handleError();
}
}
function setData(cups, temp, opa, timeFromUpdate, timeFromBrew, brewing){
if (cups == 0) {
opa = 0;
}
brewtext = "";
$("#upper").css({opacity: opa/100});
$("#scale2").css({width: Math.min(cups/9*100,100) + '%'});
$("#text,body").removeClass();
cups = Number(cups).toFixed(1);
if(timeFromUpdate > 600000){
cups = "?";
brewtext = "&infin;";
$("#text").addClass("unknown");
}
else if(brewing){
cups = "+";
brewtext = ":)";
$("#text").addClass("brewing");
}
else if(cups <= 2){
$("#text").addClass("hurry");
}
formatBrewTime();
if($("#text").html() == "+" && !brewing)
$("body").addClass("coffeeready");
var cupsString = cups.toString();
len = cupsString.length;
$("#text").html(cups);
}
function updateTime(){
var now = new Date();
$("#time").html(formatTime(now.getHours(),now.getMinutes(),now.getSeconds()));
}
function formatTime(hours, minutes, seconds){
var str = "";
if(hours < 10)
str += "0";
str += hours;
str += ":";
if(minutes < 10)
str += "0";
str += minutes;
str += ":";
if(seconds < 10)
str += "0";
str += seconds;
return str;
}
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(len);
$("#text").css({ top: s*0.16-font/2 + 'px', fontSize: font + 'px', marginLeft: -font*len*3/10 + 'px'});
}
+4 -11
View File
@@ -1,6 +1,3 @@
{% load i18n %}
{% load static %}
<html>
<head>
<title>Coffee Cups @Guild Room - AYY SIK ry</title>
@@ -13,16 +10,13 @@
<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>
<link rel="stylesheet" href="/static/css/coffee.css" />
<script src="/static/js/coffee.js"></script>
</head>
<body>
<div id="container">
<span id="brewtime" class="brewtime layerone"></span>
<span class="brewtime layertwo">:)</span>
<span id="brewtime"></span>
<span id="address">
ka.dy.fi
<noscript><br>This page uses JavaScript!</noscript>
@@ -32,8 +26,7 @@
</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">&nbsp;+</div>
<div id="text"></div>
<div id="scale"><div id="scale2"></div></div>
</div>
</div>
+28
View File
@@ -1,2 +1,30 @@
from django.test import TestCase, Client
from django.conf import settings
from coffee_scale.mqtt import on_message
HOST = settings.MQTT_SETTINGS['HOST']
PORT = settings.MQTT_SETTINGS['PORT']
TOPICS = settings.MQTT_SETTINGS['TOPICS']
class MQTTTestCase(TestCase):
"""Tests MQTT functionality"""
class MockMessage:
def __init__(self, payload, topic):
self.payload = payload
self.topic = topic
def setUp(self):
payload = '10'.encode('utf-8')
topic = TOPICS['CUPS']
msg = MQTTTestCase.MockMessage(payload, topic)
on_message(None, None, msg)
self.c = Client()
def test_receive_cups(self):
response = self.c.get('/coffee/cups')
payload = response.json()
self.assertEquals(payload['cups'], 10)
+8 -6
View File
@@ -1,12 +1,14 @@
from django.conf.urls import url
from django.conf import settings
from .views import coffee_view
from django.views.generic.base import RedirectView
from .views import coffee_view, cups_view
favicon_view = RedirectView.as_view(url='static/img/favicon.ico', permanent=True)
urlpatterns = [
# landing page
url(r'^$', coffee_view),
]
url(r'^cups', cups_view),
if settings.DEBUG:
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
urlpatterns += staticfiles_urlpatterns()
]
+17 -1
View File
@@ -3,9 +3,25 @@ from django.http import JsonResponse
from django.utils import timezone
from .mqtt import get_latest
import coffee_scale.mqtt # somehow this is needed
import logging
from django.conf import settings
def coffee_view(request):
return render(request, 'coffee_scale:coffee.html')
return render(request, 'coffee.html')
def cups_view(request):
now = timezone.now()
latest = get_latest()
data = {
'date': now,
'cups': latest.get('cups'),
'last_brew': latest.get('brew_time'),
'brewing': latest.get('brewing'),
'weight': latest.get('weight')
}
return JsonResponse(data)
+3 -3
View File
@@ -5,9 +5,9 @@ services:
image: 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"]
image: 86.50.143.82:5000/web20
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:8080"]
ports:
- "8000:8000"
- "8080:8080"
depends_on:
- db
View File
-3
View File
@@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.
-5
View File
@@ -1,5 +0,0 @@
from django.apps import AppConfig
class ExpensesClaimConfig(AppConfig):
name = 'expenses_claim'
-32
View File
@@ -1,32 +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)
email = forms.EmailField(label='Sähköposti', max_length=100)
amount = forms.DecimalField(label='Yhteensä', decimal_places=2)
def clean_iban(self):
"""Validate IBAN."""
orig_iban = self.cleaned_data['iban']
# Remove spaces.
cleaned_iban = orig_iban.replace(' ', '')
# Move first 4 symbols to the end of the string.
cleaned_iban = cleaned_iban[4:] + cleaned_iban[0:4]
LETTERS = {letter: str(index) for index,
letter in enumerate(ascii_uppercase, start=10)}
cleaned_iban = cleaned_iban.upper()
# Replace all letters with numbers, so that A=10, B=11, ..., Z=35.
cleaned_iban = [LETTERS[char] if char in LETTERS
else char for char in cleaned_iban]
cleaned_iban = ''.join(cleaned_iban)
# If cleaned_iban modulo 97 != 1 the IBAN number is invalid.
if int(cleaned_iban) % 97 != 1:
raise forms.ValidationError('Invalid IBAN number!')
return orig_iban
-3
View File
@@ -1,3 +0,0 @@
from django.db import models
# Create your models here.
@@ -1,169 +0,0 @@
* {
margin: 0;
padding: 0;
}
.heading {
padding: 20px 10px;
text-align: center;
}
.claim-form {
display: flex;
flex-direction: column;
align-items: center;
width: 100%
}
.form {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: 10px 20px;
width: 60%;
}
.table {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: space-between;
padding: 20px 10px;
}
.scrollarea {
display: flex;
flex-direction: column;
justify-content: flex-start;
overflow-y: scroll;
}
.tablerow {
display: flex;
flex-direction: row;
justify-content: space-between;
flex-wrap: wrap;
padding: 10px 5px;
}
.fieldscolumn {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-end;
}
.fieldsrow {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
align-items: stretch;
padding: 5px 10px;
}
.submit {
align-self: center;
width: 30%;
margin: 20px 10px;
}
.imageupload > input {
display: none;
}
.fieldsrow > div {
margin: 10px 20px;
}
.fieldscolumn > div {
margin: 10px 20px;
}
.fieldscolumn > div > label {
margin-right: 20px;
}
.money > input {
width: 100px;
}
#addreceipt {
height: 40px;
}
#total {
display: flex;
flex-direction: row;
justify-content: flex-end;
padding: 20px 10px;
}
#receiptslist {
height: 20rem;
}
#info2 {
justify-content: flex-end;
}
#textbox > textarea {
align-self: flex-end;
}
#id_iban {
width: 18rem;
}
.fieldscolumn > #textbox > textarea {
height: 8rem;
width: 18rem;
}
@media only screen and (max-width: 768px) {
.heading {
font-size: 18px;
padding: 5px 5px;
}
h1.heading {
visibility: hidden;
height: 0px;
}
.table {
padding: 5px 5px
}
#total {
justify-content: center;
}
.fieldsrow {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-around;
padding: 5px 5px
}
#receiptslist {
width: 12rem;
}
}
@media only screen and (max-height: 741px) {
#receiptslist {
height: 17rem;
}
}
@media only screen and (max-height: 569px) {
#receiptslist {
height: 10rem;
}
}
@@ -1,27 +0,0 @@
function addReceipt(event) {
event.preventDefault();
var receipts = document.getElementById("receiptslist");
var div2append = document.createElement("div");
var newrow = receipts.appendChild(div2append);
newrow.classList.add("tablerow");
div2append = document.createElement("div");
var div1 = newrow.appendChild(div2append);
div1.classList.add("imageupload");
div2append = document.createElement("div");
var div2 = newrow.appendChild(div2append);
div2.classList.add("desc");
div2append = document.createElement("div");
var div3 = newrow.appendChild(div2append);
div3.classList.add("money");
var file = document.getElementById("newreceipt");
var desc = document.getElementById("adddescription");
var sum = document.getElementById("addsum");
div1.appendChild(file.cloneNode(true));
div2.appendChild(document.createTextNode(desc.value));
div3.appendChild(document.createTextNode(sum.value));
var total = document.getElementById("totalsum");
total.value = Number(total.value) + Number(sum.value);
}
-19
View File
@@ -1,19 +0,0 @@
{% extends "project.html" %}
{% load static %}
{% load i18n %}
{% block styles %}
<script src="{% static "claim_form/js/addReceipt.js" %}"></script>
<link rel="stylesheet" href="{% static "claim_form/css/base.css" %}"></script>
{% endblock styles %}
{% block body %}
<div class="claim-form">
{% block content %}
{% include "expenses_claim:form.html" %}
{% endblock %}
</div>
{% endblock body %}
-20
View File
@@ -1,20 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Raha-anomus</title>
</head>
<body>
<h1>Raha-anomus</h1>
<div>
<div>
{{ name }}
</div>
<div>
{{ iban }}
</div>
<div>
{{ amount }}
</div>
</div>
</body>
</html>
-72
View File
@@ -1,72 +0,0 @@
{% load i18n %}
{% load static %}
{% block content %}
<form class="form" id="claim" action="" method="post">
<h1 class="heading">Aalto Yliopiston Sähköinsinöörikilta ry Raha-anomuslomake</h1>
<div class="fieldsrow">
<div class="imageupload" id="newreceipt">
<label for="newreceipt">
Kuitti
<img class="thumbnail" src="{% static "claim_form/img/icon.png" %}"/>
</label>
<input id="selectreceipt" type="file"/>
</div>
<div>
<label for="adddescription">
Kuvaus
</label>
<input id="adddescription" type="text">
</div>
<div class="money">
<label for="addsum">
Summa
</label>
<input type="number" id="addsum" maxlength=100/>
</div>
<div>
<button id="addreceipt" onclick="addReceipt(event)">Lisää kuitti</button>
</div>
</div>
<div class="table">
<div class="scrollarea" id="receiptslist">
<div class="tablerow">
<div class="tableelement">Kuitti</div>
<div class="tableelement">Kuvaus</div>
<div class="tableelement">Summa</div>
</div>
</div>
<div class="money" id="total">
<label for="id_amount">Yhteensä</label>
{{ form.amount }}
</div>
</div>
<div class="fieldscolumn">
<div class="fieldsrow">
<div class="fieldscolumn" id="info1">
<div id="textbox">
<label for="selite">Selite</label>
<textarea id="selite"></textarea>
</div>
<div>
<label for="id_iban">{{ form.iban.label }}</label>
{{ form.iban }}
</div>
</div>
<div class="fieldscolumn" id="info2">
<div>
<label for="id_name">{{ form.name.label }}</label>
{{ form.name }}
</div>
<div>
<label for="id_email">{{ form.email.label }}</label>
{{ form.email }}
</div>
<input class="submit" id="submitclaim" type="submit" value="Submit"/>
</div>
</div>
</div>
{% csrf_token %}
</form>
{% endblock content %}
-60
View File
@@ -1,60 +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",
'email': 'john@doe.com',
'iban': "FI37 1590 3000 0007 76",
'amount': 12.54
})
self.assertTrue(form.is_valid())
def test_valid_data2(self):
form = ExpensesClaim({
'name': "John Cena",
'email': 'john@cena.com',
'iban': "AL35202111090000000001234567",
'amount': 12
})
self.assertTrue(form.is_valid())
def test_valid_data3(self):
form = ExpensesClaim({
'name': "John Wayne",
'email': 'john@wayne.com',
'iban': "BR1500000000000010932840814P2",
'amount': 12.0
})
self.assertTrue(form.is_valid())
def test_invalid_iban(self):
form = ExpensesClaim({
'name': "John Lennon",
'email': 'john@lennon.com',
'iban': "FI3734 1590 3000 0007 76",
'amount': 12.54
})
self.assertFalse(form.is_valid())
def test_invalid_amount(self):
form = ExpensesClaim({
'name': "John Kenedy",
'email': 'john@kenedy.com',
'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",
'email': 'john@travolta.com',
'iban': "FI37 1590 3000 0007 76",
'amount': 12.544
})
self.assertFalse(form.is_valid())
-9
View File
@@ -1,9 +0,0 @@
"""Expenses claim urls."""
from django.conf.urls import url
from .views import claim
urlpatterns = [
url(r'^new', claim)
]
-41
View File
@@ -1,41 +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 webapp.utils import send_email_with_attachment
from django.template.loader import render_to_string
from weasyprint import HTML
from .forms import ExpensesClaim
@require_http_methods(["GET", "POST"])
def claim(request):
"""Render expenses claim form."""
if request.method == 'POST':
form = ExpensesClaim(request.POST)
if form.is_valid():
name = form.cleaned_data['name']
amount = form.cleaned_data['amount']
iban = form.cleaned_data['iban']
html_string = render_to_string('expenses_claim:claim2pdf.html',
{'name': name, 'iban': iban,
'amount': amount}).encode('UTF-8')
html = HTML(string=html_string)
attachment = html.write_pdf()
response = HttpResponse(
attachment, content_type='application/pdf;'
)
response['Content-Disposition'] = 'filename=claim.pdf'
email = "leo.kivikunnas@aalto.fi"
subject = "Test expenses claim"
body = "Test"
send_email_with_attachment(email, subject, body, attachment)
return response
elif request.method == 'GET':
form = ExpensesClaim()
return render(request, 'expenses_claim:base.html', {'form': form})
+4 -3
View File
@@ -1,9 +1,10 @@
"""Admin site registers."""
from django.contrib import admin
from infoscreen.models import (
Rotation, InfoItem, InfoInstance, ImageInfoItem,
ExternalImageInfoItem, ABBInfoItem, ExternalWebsiteInfoItem, VideoInfoItem)
from infoscreen.models import Rotation, InfoItem, InfoInstance
from infoscreen.models import ImageInfoItem, ExternalImageInfoItem, ABBInfoItem
from infoscreen.models import ExternalWebsiteInfoItem
from infoscreen.models import VideoInfoItem
# Register your models here.
admin.site.register(Rotation)
+69
View File
@@ -0,0 +1,69 @@
"""File containing Infoscreen HSL data fetcher classes."""
import requests
import json
import logging
import os
import pytz
from datetime import timedelta, datetime
from django.utils import timezone, dateparse
from django.utils.dateformat import format
from django.conf import settings
with open(os.path.join(settings.BASE_DIR, 'infoscreen', 'hsl_stops.graphql')) as stops_file:
STOPS_QUERY = stops_file.read()
with open(os.path.join(settings.BASE_DIR, 'infoscreen', 'hsl_stops_variables.json')) as vars_file:
STOPS_VARS = json.loads(vars_file.read())
API_URL = 'https://api.digitransit.fi/routing/v1/routers/hsl/index/graphql'
API_HEADERS = {'Content-Type': 'application/json'}
def fetch():
"""Fetch data from HSL API."""
query_vars = STOPS_VARS.copy()
query_vars['startTime_6'] = format(timezone.now(), 'U')
post_data = json.dumps({
'operationName': 'NearestRoutesContainer',
'query': STOPS_QUERY,
'variables': query_vars,
})
resp = requests.post(API_URL, data=post_data, headers=API_HEADERS)
data = resp.json()
items = data['data']['viewer']['_nearest']['edges']
places = map(lambda item: item['node']['place'], items)
schedule = []
for place in places:
route = place['pattern']['route']['shortName']
stop_times = place['_stoptimes']
for stop_time in stop_times:
timestamp = stop_time['serviceDay'] + stop_time['realtimeArrival']
headsign = stop_time['stopHeadsign']
stop_name = stop_time['stop']['name']
time_diff = (timestamp - timezone.now().timestamp()) / 60 # minutes
if time_diff < settings.HSL_DEPARTURE_THRESHOLD:
continue
elif time_diff < settings.HSL_HURRY_THRESHOLD:
time = '{} min'.format(int(time_diff))
else:
time = pytz.utc.localize(datetime.fromtimestamp(timestamp)).strftime('%H:%M')
schedule.append({
'route': route,
'headsign': headsign,
'timestamp': time,
'stop': stop_name,
})
return schedule
+71
View File
@@ -0,0 +1,71 @@
query NearestRoutesContainer($lat_0: Float!, $lon_1: Float!, $maxDistance_2: Int!, $maxResults_3: Int!, $timeRange_7: Int!, $numberOfDepartures_8: Int!, $filterByModes_4: [Mode]!, $filterByPlaceTypes_5: [FilterPlaceType]!, $startTime_6: Long!) {
viewer {
...F5
}
}
fragment F0 on DepartureRow {
_stoptimes4caEfh: stoptimes(startTime: $startTime_6, timeRange: $timeRange_7, numberOfDepartures: $numberOfDepartures_8) {
pickupType
serviceDay
realtimeDeparture
}
id
}
fragment F1 on DepartureRow {
pattern {
route {
shortName
}
}
_stoptimes: stoptimes(startTime: $startTime_6, timeRange: $timeRange_7, numberOfDepartures: $numberOfDepartures_8) {
realtimeArrival
serviceDay
stopHeadsign
stop {
name
}
}
}
fragment F2 on BikeRentalStation {
id
}
fragment F3 on placeAtDistance {
distance
place {
id
__typename
...F1
...F2
}
id
}
fragment F4 on placeAtDistanceConnection {
edges {
node {
distance
place {
id
__typename
...F0
}
id
...F3
}
cursor
}
pageInfo {
hasNextPage
hasPreviousPage
}
}
fragment F5 on QueryType {
_nearest: nearest(lat: $lat_0, lon: $lon_1, maxDistance: $maxDistance_2, maxResults: $maxResults_3, first: $maxResults_3, filterByModes: $filterByModes_4, filterByPlaceTypes: $filterByPlaceTypes_5) {
...F4
}
}
+10
View File
@@ -0,0 +1,10 @@
{
"lat_0": 60.190480099999995,
"lon_1": 24.8275665,
"maxDistance_2": 1000,
"maxResults_3": 50,
"numberOfDepartures_8": 2,
"timeRange_7": 7200,
"filterByModes_4": ["BUS"],
"filterByPlaceTypes_5": ["DEPARTURE_ROW"]
}
+10
View File
@@ -0,0 +1,10 @@
from django.core.management.base import BaseCommand
from infoscreen.hsl_fetcher import HSLFetcher
class Command(BaseCommand):
help = 'Loads HSL timetables and save to json file.'
def handle(self, *args, **options):
fetcher = HSLFetcher()
fetcher.fetch()
@@ -15,7 +15,4 @@ class Migration(migrations.Migration):
migrations.DeleteModel(
name='HSLDataModel',
),
migrations.DeleteModel(
name='HslInfoItem',
),
]
-21
View File
@@ -1,21 +0,0 @@
# Generated by Django 2.1.5 on 2019-03-26 12:49
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('infoscreen', '0006_delete_hsldatamodel'),
]
operations = [
migrations.CreateModel(
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')),
],
bases=('infoscreen.infoitem',),
),
]
+32 -30
View File
@@ -98,12 +98,12 @@ class ABBInfoItem(InfoItem):
def get_template_url(self):
"""Return ABB infoitem template url."""
return "/static/infoscreen/html/abb.html"
return "/static/html/abb.html"
@staticmethod
def get_create_template_url():
"""Call create ABB infoitem template url command."""
return "/static/infoscreen/html/abb_create.html"
return "/static/html/abb_create.html"
class ApyInfoItem(InfoItem):
@@ -113,12 +113,12 @@ class ApyInfoItem(InfoItem):
def get_template_url(self):
"""Return APY infoitem template url."""
return "/static/infoscreen/html/apy.html"
return "/static/html/apy.html"
@staticmethod
def get_create_template_url():
"""Call create APY infoitem template url command."""
return "/static/infoscreen/html/apy_create.html"
return "/static/html/apy_create.html"
class ExternalWebsiteInfoItem(InfoItem):
@@ -129,12 +129,12 @@ class ExternalWebsiteInfoItem(InfoItem):
def get_template_url(self):
"""Return external website infoitem template url."""
return "/static/infoscreen/html/external_website.html?url={}".format(self.name)
return "/static/html/external_website.html?url={}".format(self.name)
@staticmethod
def get_create_template_url():
"""Call create external website infoitem template url command."""
return "/static/infoscreen/html/external_website_create.html"
return "/static/html/external_website_create.html"
def get_dict(self):
"""Convert django model to dict and return it."""
@@ -185,25 +185,12 @@ class SossoInfoItem(InfoItem):
def get_template_url(self):
"""Return Sosso infoitem template url."""
return "/static/infoscreen/html/sosso.html"
return "/static/html/sosso.html"
@staticmethod
def get_create_template_url():
"""Call create Sosso infoitem template url command."""
return "/static/infoscreen/html/sosso_create.html"
class LunchItem(InfoItem):
"""Class for Lunch Infoscreen item."""
display_name = _("Today's lunch")
def get_template_url(self):
return "/static/infoscreen/html/lunch.html"
@staticmethod
def get_create_template_url():
return "/static/infoscreen/html/lunch_create.html"
return "/static/html/sosso_create.html"
class EventInfoItem(InfoItem):
@@ -213,12 +200,12 @@ class EventInfoItem(InfoItem):
def get_template_url(self):
"""Return Event infoitem template url."""
return "/static/infoscreen/html/events.html"
return "/static/html/events.html"
@staticmethod
def get_create_template_url():
"""Call create Event infoitem template url command."""
return "/static/infoscreen/html/events_create.html"
return "/static/html/events_create.html"
class ImageInfoItem(InfoItem):
@@ -231,12 +218,12 @@ class ImageInfoItem(InfoItem):
"""Return Image infoitem template url."""
# get param to avoid angular from optimizing same template
# with different options
return "/static/infoscreen/html/generic_image.html?img={}".format(self.name)
return "/static/html/generic_image.html?img={}".format(self.name)
@staticmethod
def get_create_template_url():
"""Call create Image infoitem template url command."""
return "/static/infoscreen/html/generic_image_create.html"
return "/static/html/generic_image_create.html"
def get_dict(self):
"""Convert django model to dict and return it."""
@@ -253,12 +240,12 @@ class VideoInfoItem(InfoItem):
def get_template_url(self):
"""Return Video infoitem template url."""
return "/static/infoscreen/html/generic_video.html?video={}".format(self.name)
return "/static/html/generic_video.html?video={}".format(self.name)
@staticmethod
def get_create_template_url():
"""Call create Video infoitem template url command."""
return "/static/infoscreen/html/generic_video_create.html"
return "/static/html/generic_video_create.html"
def get_dict(self):
"""Convert django model to dict and return it."""
@@ -267,6 +254,21 @@ class VideoInfoItem(InfoItem):
return d
class HslInfoItem(InfoItem):
"""Class for HSL Infoscreen item."""
display_name = _("HSL timetables")
def get_template_url(self):
"""Return HSL infoitem template url."""
return "/static/html/hsl.html"
@staticmethod
def get_create_template_url():
"""Call create HSL infoitem template url command."""
return "/static/html/hsl_create.html"
class ExternalImageInfoItem(InfoItem):
"""Class for External Image Infoscreen item."""
@@ -275,12 +277,12 @@ class ExternalImageInfoItem(InfoItem):
def get_template_url(self):
"""Return External Image infoitem template url."""
return "/static/infoscreen/html/generic_image.html?img={}".format(self.name)
return "/static/html/generic_image.html?img={}".format(self.name)
@staticmethod
def get_create_template_url():
"""Call create External Image infoitem template url command."""
return "/static/infoscreen/html/generic_external_image_create.html"
return "/static/html/generic_external_image_create.html"
def get_dict(self):
"""Convert django model to dict and return it."""
@@ -319,7 +321,7 @@ class ExternalImageInfoItem(InfoItem):
class InfoInstance(models.Model):
"""Class for Info instance in Infoscreen."""
rotation = models.ForeignKey('Rotation', related_name='instances', on_delete=models.CASCADE)
rotation = models.ForeignKey('Rotation', related_name='instances')
duration = models.FloatField(default=15.0) # seconds
# generic relation to some kind of InfoItem
item_id = models.PositiveIntegerField()
@@ -2,10 +2,7 @@ body {
font-family: 'Open Sans', sans-serif;
}
#header:after {
content: " ";
display: block;
clear: both;
#header {
}
#header-logo {
+74
View File
@@ -0,0 +1,74 @@
table {
font-size: 4vh;
font-family: 'Droid Sans Mono', monospace;
}
.red {
color: red;
-webkit-animation-name: blinker;
-webkit-animation-duration: 2s;
-webkit-animation-timing-function: linear;
-webkit-animation-iteration-count: infinite;
-moz-animation-name: blinker;
-moz-animation-duration: 2s;
-moz-animation-timing-function: linear;
-moz-animation-iteration-count: infinite;
animation-name: blinker;
animation-duration: 2s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
.black {
color: black;
}
@-moz-keyframes blinker {
0% { opacity: 1.0; }
50% { opacity: 0.1; }
100% { opacity: 1.0; }
}
@-webkit-keyframes blinker {
0% { opacity: 1.0; }
50% { opacity: 0.1; }
100% { opacity: 1.0; }
}
@keyframes blinker {
0% { opacity: 1.0; }
50% { opacity: 0.1; }
100% { opacity: 1.0; }
}
thead{
background: #f0f0f0;
}
.header-row{
background: #f0f0f0;
font-size: 7vh;
font-family: 'Droid Sans Mono', monospace;
text-align: center;
}
.container {
width: 100vw;
padding: 0 0 0 0;
}
.container .table {
margin-left: 0;
margin-right: 0;
}
.repeat-item.ng-leave {
}
.repeat-item.ng-leave.ng-leave-active {
opacity: 0;
font-size: 0vh;
}
.repeat-item.ng-leave{
opacity: 1;
font-size: 5vh;
}
@@ -5,7 +5,6 @@ html {
body {
padding: 1.5rem;
margin: 0.5rem;
height: 100%;
}
tbody {
@@ -50,18 +49,7 @@ td {
}
}
#header {
max-width: 100%;
display: flex;
justify-content: flex-end;
}
.tab-content {
margin-top: 1rem;
}
.rotation-title-row {
display: flex;
justify-content: space-between;
align-items: center;
}
@@ -19,7 +19,7 @@
.article-thumb-col {
max-height: 200px;
text-align: left;
text-align: right;
}
.article-title-col {
@@ -1,10 +1,10 @@
<link rel="stylesheet" href="/static/infoscreen/css/abb.css">
<link rel="stylesheet" href="/static/css/abb.css">
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
<div ng-controller="ABBController">
<!-- Only show the job listing if there are any jobs, i.e, the jobs list is non-empty -->
<div id="header" class="row" ng-if="jobs.length > 0">
<div id="header-logo">
<img src="/static/infoscreen/img/ABB_logo.png">
<img src="/static/img/ABB_logo.png">
</div>
<div id="header-title">
TYÖPAIKAT
@@ -28,6 +28,6 @@
<!-- If there are no jobs, show a static image -->
<div class="row" ng-if="jobs.length == 0">
<img src="/static/infoscreen/img/ABB_uralle.png" style="position:absolute;left:0;top:0;width:100vw;">
<img src="/static/img/ABB_uralle.png" style="position:absolute;left:0;top:0;width:100vw;">
</div>
</div>
@@ -4,7 +4,7 @@
</div>
<div class="form-group">
<label>Name:</label>
<input class="form-control" type="text" ng-model="item.name"></input>
<input type="text" ng-model="item.name"></input>
</div>
<input type="button" class="btn btn-success" ng-click="send()" value="create"></input>
</div>
@@ -1,4 +1,4 @@
<link rel="stylesheet" href="/static/infoscreen/css/apy.css">
<link rel="stylesheet" href="/static/css/apy.css">
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro" rel="stylesheet">
<div id="bg">
<div class="container " ng-controller="ApyController">
@@ -1,10 +1,10 @@
<div ng-controller="infoadmin_apyitem_create" style="margin-top:20px;">
<div>
Create new ÄPY statistics item
create apyitem
</div>
<div class="form-group">
<label>Name:</label>
<input class="form-control" type="text" ng-model="item.name"></input>
<input type="text" ng-model="item.name"></input>
</div>
<input type="button" class="btn btn-success" ng-click="send()" value="create"></input>
</div>
+4
View File
@@ -0,0 +1,4 @@
<link rel="stylesheet" href="/static/css/coffee.css">
<iframe src="https://host2.kilta.aalto.fi/kahvi/cups" allowfullscreen=true sandbox="allow-scripts allow-pointer-lock allow-same-origin">
<p>Your browser does not support iframes.</p>
</iframe>
@@ -1,4 +1,4 @@
<link rel="stylesheet" href="/static/infoscreen/css/events.css">
<link rel="stylesheet" href="/static/css/events.css">
<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">
@@ -4,7 +4,7 @@
</div>
<div class="form-group">
<label>Name:</label>
<input type="text" class="form-control" ng-model="item.name"></input>
<input type="text" ng-model="item.name"></input>
</div>
<input type="button" class="btn btn-success" ng-click="send()" value="create"></input>
</div>
@@ -1,4 +1,4 @@
<link rel="stylesheet" href="/static/infoscreen/css/external_website.css">
<link rel="stylesheet" href="/static/css/external_website.css">
<iframe ng-src="{{ url | trusted_url }}" allowfullscreen=true sandbox="allow-scripts allow-pointer-lock allow-same-origin">
<p>Your browser does not support iframes.</p>
</iframe>
@@ -1,14 +1,14 @@
<div ng-controller="infoadmin_websiteitem_create" style="margin-top:20px;">
<div>
Create new item to show external website. For example "https://ka.dy.fi".
Create new item to show external website. For example "ka.dy.fi".
</div>
<div class="form-group">
<label>Name:</label>
<input type="text" class="form-control" ng-model="item.name"></input>
<input type="text" ng-model="item.name"></input>
</div>
<div class="form-group">
<label>Url:</label>
<input type="text" class="form-control" ng-model="item.url"></input>
<input type="text" ng-model="item.url"></input>
</div>
<input type="button" class="btn btn-success" ng-click="send()" value="create"></input>
</div>
@@ -4,11 +4,11 @@
</div>
<div class="form-group">
<label>Name:</label>
<input type="text" class="form-control" ng-model="item.name"></input>
<input type="text" ng-model="item.name"></input>
</div>
<div class="form-group">
<label>Url:</label>
<input type="text" class="form-control" ng-model="item.url"></input>
<input type="text" ng-model="item.url"></input>
</div>
<input type="button" class="btn btn-success" ng-click="send()" value="create"></input>
</div>
@@ -4,7 +4,7 @@
</div>
<div class="form-group">
<label>Name:</label>
<input type="text" class="form-control" ng-model="imagename"></input>
<input type="text" ng-model="imagename"></input>
</div>
<div class="form-group">
<input type="file" ngf-select ng-model="img" name="file" required>
@@ -1,4 +1,4 @@
<link rel="stylesheet" href="/static/infoscreen/css/video.css">
<link rel="stylesheet" href="/static/css/video.css">
<div class="fullscreen-bg">
@@ -4,7 +4,7 @@
</div>
<div class="form-group">
<label>Name:</label>
<input type="text" class="form-control" ng-model="name"></input>
<input type="text" ng-model="name"></input>
</div>
<div class="form-group">
<input type="file" ngf-select ng-model="video" name="file" required>
+41
View File
@@ -0,0 +1,41 @@
<link rel="stylesheet" href="/static/css/hsl.css">
<link href="https://fonts.googleapis.com/css?family=Droid+Sans+Mono" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.19.1/locale/fi.js"></script>
<div class="container" ng-app="myApp" ng-controller="timetableCtrl">
<div class="header-row row">
<div class="col-sm-2"><p>{{clock | date:'HH:mm'}}</p></div>
<div class="col-sm-8">HSL-Aikataulut</div>
<div class="col-sm-2 time"></div>
</div>
<h1 style="font-size: 10vh; text-align: center" ng-if="error">
{{error}}
</h1>
<table ng-if="!error" class="table table-striped row">
<thead>
<tr>
<th>
Aika
</th>
<th>
Linja
</th>
<th>
Pys&#228;kki
</th>
</tr>
</thead>
<tbody>
<tr class="repeat-item" ng-repeat="x in stoptimes | orderBy: ['timestamp'] | limitTo: 11">
<td style="min-width: 300px">
{{x.timestamp}}
</td>
<td>
<strong>{{x.route}}</strong>, {{x.headsign}}
</td>
<td>
{{x.stop}}
</td>
</tr>
</tbody>
</table>
</div>
+10
View File
@@ -0,0 +1,10 @@
<div ng-controller="infoadmin_hslitem_create" style="margin-top:20px;">
<div>
Create new item to show hsl ttimetables. Name is used only as identifier
</div>
<div class="form-group">
<label>Name:</label>
<input type="text" ng-model="item.name"></input>
</div>
<input type="button" class="btn btn-success" ng-click="send()" value="create"></input>
</div>
+20
View File
@@ -0,0 +1,20 @@
<link rel="stylesheet" href="/static/css/sosso.css">
<div ng-controller="SossoController">
<div id="header">
<img id="header-image" src="/static/img/sossoheader.png" >
</div>
<div id="container">
<div class="article-row row" ng-repeat="post in data.posts">
<div class="article-thumb-col col-md-6">
<img class="thumbnail" ng-src="{{ post.thumbnail_images['mh-edition-lite-medium'].url }}">
</div>
<div>
<h1 class="col-md-6">
<div class="article-title-col">
{{ post.title }}
</div>
</h1>
</div>
</div>
</div>
</div>
@@ -4,7 +4,7 @@
</div>
<div class="form-group">
<label>Name:</label>
<input type="text" class="form-control" ng-model="item.name"></input>
<input type="text" ng-model="item.name"></input>
</div>
<input type="button" class="btn btn-success" ng-click="send()" value="create"></input>
</div>
+1
View File
@@ -0,0 +1 @@
<h1>testi2</h1>
+1
View File
@@ -0,0 +1 @@
<h1>testi3</h1>

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

@@ -1,53 +0,0 @@
#header {
height: 30%;
width: 100%;
background-color:#7c1330;
text-align: center;
}
#header-image {
display: block;
margin-left: auto;
margin-right: auto;
max-width: 100%;
}
.article-row {
min-height: 20vh;
margin: 10px 10px 10px 10px;
}
.article-thumb-col {
max-height: 200px;
text-align: left;
}
.article-title-col {
font-size: 3vw;
}
.thumbnail {
max-width: 355px;
max-height: 200px;
}
#sossoimage {
height: 300px;
position: relative;
left: 0px;
top: 0px;
}
.stretch {
width:100%;
height:100%;
}
#post {
height: 540px;
border:2px solid black;
}
#container {
max-height: 70%;
}
@@ -1,11 +0,0 @@
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">
<link rel="stylesheet" href="/static/infoscreen/css/lunch.css">
<div ng-controller="LunchController">
<div id="container">
<div class="restaurant row" ng-repeat="restaurant in data">
<div class="lunch-option" ng-repeat="l in lunch">
<h3 ng-bind-html="l.title | unsafe"></h3>
</div>
</div>
</div>
</div>
@@ -1,10 +0,0 @@
<div ng-controller="infoadmin_lunchitem_create" style="margin-top:20px;">
<div>
Create new item to show restaurants. Name is used only as identifier
</div>
<div class="form-group">
<label>Name:</label>
<input type="text" class="form-control" ng-model="item.name"></input>
</div>
<input type="button" class="btn btn-success" ng-click="send()" value="create"></input>
</div>
@@ -1,17 +0,0 @@
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">
<link rel="stylesheet" href="/static/infoscreen/css/sosso.css">
<div ng-controller="SossoController">
<div id="header">
<img id="header-image" src="/static/infoscreen/img/sossoheader.png" >
</div>
<div id="container">
<div class="article-row row" ng-repeat="post in data.posts">
<div class="article-thumb-col col-md-4">
<img class="thumbnail" ng-src="{{ post.thumbnail_images['mh-edition-lite-medium'].url }}">
</div>
<div class="col-md-8 article-title-col">
<h1 ng-bind-html="post.title | unsafe"></h1>
</div>
</div>
</div>
</div>
@@ -182,8 +182,8 @@ var simple_controllers = [
"external_image",
"abbitem",
"sossoitem",
"lunchitem",
"eventitem",
"hslitem",
"websiteitem",
"apyitem",
];
@@ -46,13 +46,6 @@ app.filter('trusted_url', ['$sce', function ($sce) {
};
}]);
//Used for special characters in Sosso. This may open up XSS, so we need to trust that sosso.fi doesn't get compromised...
app.filter('unsafe', function($sce) {
return function(val) {
return $sce.trustAsHtml(val);
};
});
app.controller('ABBController', function($scope, $http){
$scope.jobs = [];
var min_date = moment().subtract(30,'days').format("YYYY-MM-DD%20HH:mm:ss");
@@ -82,21 +75,6 @@ app.controller('SossoController', function($scope, $http) {
})
});
app.controller('LunchController', function ($scope, $http) {
$scope.data = [];
var restaurants = [42];
var restaurant_names = ["TUAS"]
var cur_date = new Date().toISOString().split("T")[0]
$http.get("https://kitchen.kanttiinit.fi/menus?restaurants=" + restaurants.join(",") + "&days=" + cur_date).then(function (response) {
$scope.data = restaurant_names.map(function(n, idx) {
return {
name: n,
lunch: response[idx][cur_date],
}
});
})
});
app.controller('ApyController', function($scope, $http) {
$scope.items = [];
$http.get("/infoscreen/apyjson").then(function(response)
@@ -134,3 +112,35 @@ app.filter('unixTimeToDifference', function() {
return res;
}
})
app.controller('timetableCtrl',
function($scope, $http, $interval) {
function load() {
$http.get('/infoscreen/hsl_data')
.then(function(data, status, headers, config) { //eslint-disable-line no-unused-vars
$scope.stoptimes = data.data;
$scope.error = data.data.error || null;
});
$http.get('/infoscreen/hsl_data/settings')
.then(function(data, status, headers, config) { //eslint-disable-line no-unused-vars
$scope.departureThreshold = data.data['departure_threshold'];
$scope.hurryThreshold = data.data['hurry_threshold'];
});
}
function update_clock() {
$scope.clock = Date.now();
}
$scope.$on('$destroy', function() {
$interval.cancel(load_interval);
$interval.cancel(clock_interval);
});
var load_interval = $interval(load, 5000);
var clock_interval = $interval(update_clock, 1000);
update_clock();
load();
}
);
-44
View File
@@ -1,44 +0,0 @@
{% load i18n %}
{% load static %}
{% load staticfiles %}
<!DOCTYPE html>
{% block html %}
<html ng-app="{% block appname %}{% endblock appname %}">
<head>
<meta charset="utf-8" name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1" />
<title>
{% block title %}
{% endblock title %}
</title>
{% block libraries %}
<script src="{% static "js/lib/angular.js" %}"></script>
<script src="{% static "js/lib/ng-file-upload-bower-12.2.11/ng-file-upload-all.js" %}"></script>
<script src="{% static "js/lib/jquery-3.1.0.min.js" %}"></script>
<script src="{% static "js/lib/underscore-min.js" %}"></script>
<script src="{% static "js/lib/moment.js" %}"></script>
<script src="{% static "js/lib/angular-route.js" %}"></script>
<script src="{% static "js/lib/angular-animate.js" %}"></script>
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
{% endblock libraries %}
{% block styles %}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link rel="stylesheet" href="{% static "webapp/css/footer.css" %}">
{% endblock styles %}
{% block controllers %}
<script src="{% static "infoscreen/js/infoadmin_controllers.js" %}"></script>
{% endblock controllers %}
</head>
<body>
{% block body %}
{% endblock body %}
</body>
</html>
{% endblock html %}
-7
View File
@@ -1,7 +0,0 @@
{% load i18n %}
{% load static %}
<div class="header-content">
<div class="logo">
<a href="/"><img src="{% static "webapp/img/logo_header.png" %}" alt="Aalto-yliopiston Sähköinsinöörikilta ry"></a>
</div>
</div>
+148 -39
View File
@@ -1,45 +1,154 @@
{% extends "infoscreen:base.html" %}
{% load i18n %}
{% load static %}
{% load staticfiles %}
<!DOCTYPE html>
<html ng-app="infoAdmin">
<head>
<meta charset="utf-8" name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1" />
<title>Infoscreen admin</title>
<script src="{% static "js/lib/angular.js" %}"></script>
<script src="{% static "js/lib/ng-file-upload-bower-12.2.11/ng-file-upload-all.js" %}"></script>
<script src="{% static "js/lib/jquery-3.1.0.min.js" %}"></script>
<script src="{% static "js/lib/bootstrap.min.js" %}"></script>
<script src="{% static "js/lib/underscore-min.js" %}"></script>
<script src="{% static "js/infoadmin_controllers.js" %}"></script>
{% block appname %}infoAdmin{% endblock appname %}
{% block title %}
{% trans "Infoscreen admin" %}
{% endblock title %}
{% block styles %}
{{ block.super }}
<link rel="stylesheet" href="{% static "infoscreen/css/admin.css" %}"></link>
{% endblock styles %}
{% block controllers %}
<script src="{% static "infoscreen/js/infoadmin_controllers.js" %}"></script>
{% endblock controllers %}
{% block body %}
<div id="header" class="row">
<div class="logout-button">
<form action="/admin/logout/" method="post"> {% csrf_token %}
<input type="Submit" value="{% trans "Log out" %}" name="Logout" class="btn btn-danger"/>
</form>
</div>
</div>
<div class="container" ng-controller="infoadmin_ctrl">
<div class="row">
<div class="col">
<h1>{% trans "Infoscreen Admin Pane" %}</h1>
<link rel="stylesheet" href="{% static "css/lib/bootstrap.min.css" %}"></link>
<link rel="stylesheet" href="{% static "css/base.css" %}"></link>
<link rel="stylesheet" href="{% static "css/infoscreen_admin.css" %}"></link>
</head>
<body>
<div id="header" class="row">
<div class="logout-button">
<form action="/logout" method="post"> {% csrf_token %}
<input type="Submit" value="{% trans "Log out" %}" name="Logout" class="btn btn-danger"/>
</form>
</div>
</div>
</div>
{% include "infoscreen:nav.html" %}
<div class="tab-content" id="tabContent">
{% include "infoscreen:tabs/slides.html" %}
{% include "infoscreen:tabs/rotations.html" %}
{% include "infoscreen:tabs/add_remove.html" %}
</div>
</div>
<div class="container" ng-controller="infoadmin_ctrl">
<div class="row">
<div class="col-md-12">
<h1>{% trans "Infoscreen Admin Pane" %}</h1>
</div>
</div>
<ul class="nav nav-tabs" role="tablist">
<li class="active"><a data-toggle="tab" href="#slides" role="tab">{% trans "Manage Slides" %}</a></li>
<li class="dropdown">
<a data-toggle="dropdown" href="#">{% trans "Manage Rotations" %}<span class="caret"></span></a>
<ul class="dropdown-menu">
<li ng-repeat="r in rotations"><a href="#rotations" ng-click="selectRotation(r.id)" data-toggle="tab">{$ r.name $}</a></li>
<li class="divider">
<li class="nav-item"><a data-toggle="tab" href="#deleterot" role="tab">{% trans "Create/Delete" %}</a></li>
</ul>
</ul>
<div class="tab-content row">
<div id="slides" class="tab-pane active">
<div ng-controller="infoadmin_ctrl">
<div class="col-xs-12 col-md-6">
<h2>{% trans "Create new item" %}</h2>
<div>{% trans "Create a new item by type" %}</div>
<table class="table table-striped">
<tr>
<td>{% trans "Item type" %}</td>
<td>
<select class="form-control form-control-sm" ng-model="createtype", ng-options="t.name for t in infotypes | orderBy: 'name'">
<option value=""></option>
</select>
</td>
</tr>
</table>
<div ng-include="createtype.create_template_url"></div>
</div>
<div class="col-xs-12 col-md-6">
<h2>{% trans "Info items" %}</h2>
<div>{% trans "Infoitems available for rotations" %}</div>
<table class="table table-striped">
<tr>
<th>{% trans "Item" %}</th>
<th>{% trans "Type" %}</th>
<th>{% trans "Delete" %}</th>
</tr>
<tr ng-repeat="i in infoitems | orderBy:['display_name','name']">
<td>{$ i.name $}</td>
<td>{$ i.display_name $}</td>
<td><input type="button" class="btn btn-danger" ng-click="deleteItem(i.item_type, i.id);" value="{% trans "Delete" %}"></input></td>
</tr>
</table>
{% include "webapp:footer.html" %}
{% endblock body %}
</div>
</div>
</div>
<div id="rotations" class="tab-pane" ng-controller="infoadmin_ctrl">
<div class="col-xs-12 col-md-6">
<h2>{% trans "Info items" %}</h2>
<div>{% trans "Infoitems available for rotations" %}</div>
<table class="table table-striped">
<tr>
<th>{% trans "Item" %}</th>
<th>{% trans "Type" %}</th>
<th>{% trans "Set duration" %}</th>
<th>{% trans "Add to rotation" %}</th>
<th>{% trans "Delete" %}</th>
</tr>
<tr ng-repeat="i in infoitems | orderBy:['display_name','name']">
<td>{$ i.name $}</td>
<td>{$ i.display_name $}</td>
<td><input type="number" min="1" max="60" class="form-control" ng-model="i.duration"></input></td>
<td><input type="button" class="btn btn-success" ng-click="createInstance(selected_rot.id, i.id, i.item_type, i.duration);" value="{% trans "Add" %}"></input></td>
<td><input type="button" class="btn btn-danger" ng-click="deleteItem(i.item_type, i.id);" value="{% trans "Delete" %}"></input></td>
</tr>
</table>
</div>
<div class="col-xs-12 col-md-6">
<h2>{% trans "Rotation" %}: {$ selected_rot.name $}<a href="/infoscreen/{$ selected_rot.id $}"><input type="button" class="btn btn-primary pull-right" value="{% trans "Preview" %}"></a></h2>
<div>{% trans "Instances in currently selected rotation" %}:</div>
<table class="table table-striped">
<tr>
<th>{% trans "Instance" %}</th>
<th>{% trans "Duration" %}</th>
<th>{% trans "Delete" %}</th>
</tr>
<tr ng-repeat="i in selected_rot.instances">
<td>{$ i.item.name $}</td><td>{$ i.duration $} s</td>
<td><input type="button" ng-click="deleteInstance(i.id);" value="{% trans "Delete" %}" class="btn btn-danger"></input></td>
</tr>
</table>
</div>
</div>
<div id="deleterot" class="tab-pane">
<div class="col-xs-12 " ng-controller="infoadmin_ctrl">
<h2>{% trans "Rotations" %}</h2>
<div>
{% trans "Select rotation to edit" %}:
</div>
<table class="table table-striped">
<tr>
<th>{% trans "Rotation" %}</th>
<th>{% trans "id" %}</th>
<th>{% trans "Preview" %}</th>
<th>{% trans "Delete" %}</th>
</tr>
<tr ng-repeat="r in rotations">
<td>{$ r.name $}</td>
<td>{$ r.id $}</td>
<td><a href="/infoscreen/{$ r.id $}"><input type="button" class="btn btn-primary" value="{% trans "Preview" %}"></a></td>
<td><input type="button" class="btn btn-danger" ng-click="deleteRotation(r.id)" value="{% trans "Delete" %}"></input></td>
</tr>
<tr>
<td><input type="text" class="form-control" ng-model="r.name" placeholder="{% trans "Name" %}"></input></td>
<td><input type="button" class="btn btn-success" ng-click="createRotation(r.name)" value="{% trans "Create new" %}"></input></td>
<td></td>
<td></td>
</tr>
</table>
</div>
</div>
</div>
<div style="margin-top: 100px;">
{% include "footer.html" %}
</div>
</div>
</body>
</html>
+25 -21
View File
@@ -1,25 +1,29 @@
{% extends "infoscreen:base.html" %}
{% load static %}
{% load i18n %}
{% block appname %}infoApp{% endblock appname %}
<!DOCTYPE html>
<html ng-app="infoApp">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
{% block title %}
{% trans "Infoscreen" %}
{% endblock title %}
<title>Infoscreen</title>
<script src="{% static "js/lib/moment.js" %}"></script>
<script src="{% static "js/lib/angular.js" %}"></script>
<script src="{% static "js/lib/angular-route.js" %}"></script>
<script src="{% static "js/lib/angular-animate.js" %}"></script>
<script src="{% static "js/lib/jquery-3.1.0.min.js" %}"></script>
<script src="{% static "js/lib/bootstrap.min.js" %}"></script>
<script src="{% static "js/lib/underscore-min.js" %}"></script>
<script src="{% static "js/infoscreen_controllers.js" %}"></script>
{% block controllers %}
<script src="{% static "infoscreen/js/infoscreen_controllers.js" %}"></script>
{% endblock controllers %}
{% block styles %}
<link rel="stylesheet" href="{% static "infoscreen/css/infoscreen.css" %}"></link>
{% endblock styles %}
{% block body %}
<div class="container ng-scope" ng-controller="infoscreen_main" ng-init="init({{ rotation }})">
<div ng-animate-swap="index" class="cell swap-animation">
<div id="infocontent" ng-include="active.template" onload="active.onload()"></div>
</div>
</div>
{% endblock body %}
<link rel="stylesheet" href="{% static "css/lib/bootstrap.min.css" %}"></link>
<link rel="stylesheet" href="{% static "css/infoscreen.css" %}"></link>
</head>
<body>
<div class="container ng-scope" ng-controller="infoscreen_main" ng-init="init({{ rotation }})">
<div ng-animate-swap="index" class="cell swap-animation">
<div id="infocontent" ng-include="active.template" onload="active.onload()"></div>
</div>
</div>
</body>
</html>
-17
View File
@@ -1,17 +0,0 @@
{% load i18n %}
<ul class="nav nav-tabs" id="tabs" role="tablist">
<li class="nav-item">
<a class="nav-link active" data-toggle="tab" role="tab" href="#slides">{% trans "Manage Slides" %}</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" role="button" data-toggle="dropdown" href="#" aria-haspopup="true" aria-expanded="false">
{% trans "Manage Rotations" %}<span class="caret"></span>
</a>
<div class="dropdown-menu">
<a class="dropdown-item" role="tab" href="#rotations" ng-repeat="r in rotations" ng-click="selectRotation(r.id)" data-toggle="tab">{$ r.name $}</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" data-toggle="tab" href="#deleterot" role="tab">{% trans "Create/Delete" %}</a>
</div>
</li>
</ul>
-30
View File
@@ -1,30 +0,0 @@
{% load i18n %}
<div id="deleterot" class="tab-pane fade" role="tabpanel">
<div class="col" ng-controller="infoadmin_ctrl">
<h2>{% trans "Rotations" %}</h2>
<div>
{% trans "Select rotation to edit" %}:
</div>
<table class="table table-striped">
<tr>
<th>{% trans "Rotation" %}</th>
<th>{% trans "id" %}</th>
<th>{% trans "Preview" %}</th>
<th>{% trans "Delete" %}</th>
</tr>
<tr ng-repeat="r in rotations">
<td>{$ r.name $}</td>
<td>{$ r.id $}</td>
<td><a class="btn btn-primary" href="/infoscreen/{$ r.id $}">{% trans "Preview" %}</a></td>
<td><a class="btn btn-danger" href="#" ng-click="deleteRotation(r.id)">{% trans "Delete" %}</a></td>
</tr>
<tr>
<td><input type="text" class="form-control" ng-model="r.name" placeholder="{% trans "Name" %}"></input></td>
<td><input type="button" class="btn btn-success" ng-click="createRotation(r.name)" value="{% trans "Create new" %}"></input></td>
<td></td>
<td></td>
</tr>
</table>
</div>
</div>
-44
View File
@@ -1,44 +0,0 @@
{% load i18n %}
<div id="rotations" class="tab-pane fade" role="tabpanel" ng-controller="infoadmin_ctrl">
<div class="col">
<h2>{% trans "Info items" %}</h2>
<div>{% trans "Infoitems available for rotations" %}</div>
<table class="table table-striped">
<tr>
<th>{% trans "Item" %}</th>
<th>{% trans "Type" %}</th>
<th>{% trans "Set duration" %}</th>
<th>{% trans "Add to rotation" %}</th>
<th>{% trans "Delete" %}</th>
</tr>
<tr ng-repeat="i in infoitems | orderBy:['display_name','name']">
<td>{$ i.name $}</td>
<td>{$ i.display_name $}</td>
<td><input type="number" min="1" max="60" class="form-control" ng-model="i.duration"></input></td>
<td><input type="button" class="btn btn-success" ng-click="createInstance(selected_rot.id, i.id, i.item_type, i.duration);" value="{% trans "Add" %}"></input></td>
<td><input type="button" class="btn btn-danger" ng-click="deleteItem(i.item_type, i.id);" value="{% trans "Delete" %}"></input></td>
</tr>
</table>
</div>
<div class="col">
<div class="rotation-title-row">
<h2>{% trans "Rotation" %}: {$ selected_rot.name $}</h2>
<a class="btn btn-primary" href="/infoscreen/{$ selected_rot.id $}">{% trans "Preview" %}</a>
</div>
<div>{% trans "Instances in currently selected rotation" %}:</div>
<table class="table table-striped">
<tr>
<th>{% trans "Instance" %}</th>
<th>{% trans "Duration" %}</th>
<th>{% trans "Delete" %}</th>
</tr>
<tr ng-repeat="i in selected_rot.instances">
<td>{$ i.item.name $}</td><td>{$ i.duration $} s</td>
<td><input type="button" ng-click="deleteInstance(i.id);" value="{% trans "Delete" %}" class="btn btn-danger"></input></td>
</tr>
</table>
</div>
</div>
-37
View File
@@ -1,37 +0,0 @@
{% load i18n %}
<div id="slides" class="tab-pane fade show active" role="tabpanel">
<div ng-controller="infoadmin_ctrl">
<div class="col">
<h2>{% trans "Create new item" %}</h2>
<div>{% trans "Create a new item by type" %}</div>
<table class="table table-striped">
<tr>
<td>{% trans "Item type" %}</td>
<td>
<select class="form-control form-control-sm" ng-model="createtype", ng-options="t.name for t in infotypes | orderBy: 'name'">
<option value=""></option>
</select>
</td>
</tr>
</table>
<div ng-include="createtype.create_template_url"></div>
</div>
<div class="col">
<h2>{% trans "Info items" %}</h2>
<div>{% trans "Infoitems available for rotations" %}</div>
<table class="table table-striped">
<tr>
<th>{% trans "Item" %}</th>
<th>{% trans "Type" %}</th>
<th>{% trans "Delete" %}</th>
</tr>
<tr ng-repeat="i in infoitems | orderBy:['display_name','name']">
<td>{$ i.name $}</td>
<td>{$ i.display_name $}</td>
<td><input type="button" class="btn btn-danger" ng-click="deleteItem(i.item_type, i.id);" value="{% trans "Delete" %}"></input></td>
</tr>
</table>
</div>
</div>
</div>
+7 -7
View File
@@ -1,7 +1,6 @@
"""File containing infoscreen urls."""
from django.conf.urls import url
from django.conf import settings
from infoscreen.views import index
from infoscreen.views import admin
@@ -18,12 +17,14 @@ from infoscreen.views import create_image_item
from infoscreen.views import create_video_item
from infoscreen.views import createABBItem
from infoscreen.views import createSossoItem
from infoscreen.views import createLunchItem
from infoscreen.views import createHslItem
from infoscreen.views import createEventItem
from infoscreen.views import createExternalWebsiteItem
from infoscreen.views import create_rotation
from infoscreen.views import delete_rotation
from infoscreen.views import CurrentHSLView
from infoscreen.views import createApyItem
from infoscreen.views import hsl_timetable_settings
from infoscreen.views import get_apy_json
urlpatterns = [
@@ -42,15 +43,14 @@ urlpatterns = [
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_hslitem$', createHslItem),
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'^hsl_data$', CurrentHSLView),
url(r'^hsl_data/settings$', hsl_timetable_settings),
url(r'^apyjson', get_apy_json),
]
if settings.DEBUG:
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
urlpatterns += staticfiles_urlpatterns()
]
+24 -21
View File
@@ -6,7 +6,6 @@ from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.decorators.http import require_http_methods
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.decorators import permission_required, login_required
from django.db import DatabaseError
from infoscreen.models import UploadFileForm
import sikweb.settings as settings
@@ -15,17 +14,21 @@ import logging
import threading
import requests
from infoscreen.models import (
Rotation, InfoItem, InfoInstance, ABBInfoItem, ExternalImageInfoItem,
ImageInfoItem, SossoInfoItem, LunchItem, EventInfoItem,
ExternalWebsiteInfoItem, ImageUploadForm, ApyInfoItem, VideoInfoItem)
from infoscreen.models import Rotation, InfoItem, InfoInstance
from infoscreen.models import (ABBInfoItem, ExternalImageInfoItem,
ImageInfoItem, SossoInfoItem, HslInfoItem)
from infoscreen.models import EventInfoItem
from infoscreen.models import ExternalWebsiteInfoItem
from infoscreen.models import ImageUploadForm
from infoscreen.models import ApyInfoItem
from infoscreen.models import VideoInfoItem
@login_required(login_url='/admin/login')
@login_required(login_url='/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_admin.html', {})
def create_item_generator(model):
@@ -33,12 +36,12 @@ def create_item_generator(model):
@ensure_csrf_cookie
@require_http_methods(["POST"])
@login_required(login_url='/admin/login')
@login_required(login_url='/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:
except ValueError:
return HttpResponseBadRequest(
'{"status":"failure","error":"invalid json supplied"}')
try:
@@ -55,7 +58,7 @@ def delete_item_generator(model):
@ensure_csrf_cookie
@require_http_methods(["DELETE"])
@login_required(login_url='/admin/login')
@login_required(login_url='/login')
@permission_required('infoscreen.delete_infoinstance', raise_exception=True)
def delete_item(request, *args, **kwargs):
idx = kwargs.pop("idx", 0)
@@ -68,7 +71,7 @@ def delete_item_generator(model):
try:
item.delete()
return HttpResponse('{"status":"success"}')
except DatabaseError:
except:
resp = HttpResponse('{"error" : "could not delete item"}')
resp.status_code = 500
return resp
@@ -77,7 +80,7 @@ def delete_item_generator(model):
# due to model structure this is little complicated
@ensure_csrf_cookie
@login_required(login_url='/admin/login')
@login_required(login_url='/login')
@permission_required('infoscreen.delete_infoinstance', raise_exception=True)
@require_http_methods(["DELETE"])
def delete_info_item(request, *args, **kwargs):
@@ -94,7 +97,7 @@ def delete_info_item(request, *args, **kwargs):
try:
item.delete()
return HttpResponse('{"status":"success"}')
except DatabaseError:
except:
resp = HttpResponse('{"error" : "could not delete item"}')
resp.status_code = 500
return resp
@@ -102,7 +105,7 @@ def delete_info_item(request, *args, **kwargs):
@require_http_methods(["POST"])
@ensure_csrf_cookie
@login_required(login_url='/admin/login')
@login_required(login_url='/login')
@permission_required('infoscreen.add_infoinstance', raise_exception=True)
def create_image_item(request, *args, **kwargs):
"""Create image Infoscreen item."""
@@ -119,7 +122,7 @@ def create_image_item(request, *args, **kwargs):
@require_http_methods(["POST"])
@ensure_csrf_cookie
@login_required(login_url='/admin/login')
@login_required(login_url='/login')
@permission_required('infoscreen.add_infoinstance', raise_exception=True)
def create_video_item(request, *args, **kwargs):
"""Create video Infoscreen item."""
@@ -136,20 +139,20 @@ def create_video_item(request, *args, **kwargs):
@require_http_methods(["POST"])
@ensure_csrf_cookie
@login_required(login_url='/admin/login')
@login_required(login_url='/login')
@permission_required('infoscreen.add_rotation', raise_exception=True)
def create_rotation(request, *args, **kwargs):
"""Create rotation."""
try:
data = json.loads(request.body.decode("utf-8"))
except json.JSONDecodeError:
except:
return HttpResponse('{"error": "bad post body!"}', status=400)
try:
name = data["name"]
Rotation.objects.create(name=name)
resp = HttpResponse(status=200)
except DatabaseError:
except:
resp = HttpResponse(
'{"error" : "could not create rotation!"}', status=400)
@@ -158,7 +161,7 @@ def create_rotation(request, *args, **kwargs):
@require_http_methods(["DELETE"])
@ensure_csrf_cookie
@login_required(login_url='/admin/login')
@login_required(login_url='/login')
@permission_required('infoscreen.delete_rotation', raise_exception=True)
def delete_rotation(request, *args, **kwargs):
"""Delete rotation."""
@@ -168,7 +171,7 @@ def delete_rotation(request, *args, **kwargs):
try:
Rotation.objects.filter(id=id).delete()
resp = HttpResponse(status=200)
except DatabaseError:
except:
resp = HttpResponse(
'{"error" : "could not delete rotation!"}', status=400)
@@ -179,7 +182,7 @@ createInstance = create_item_generator(InfoInstance)
deleteInstance = delete_item_generator(InfoInstance)
createABBItem = create_item_generator(ABBInfoItem)
createSossoItem = create_item_generator(SossoInfoItem)
createLunchItem = create_item_generator(LunchItem)
createHslItem = create_item_generator(HslInfoItem)
createExternalImageInfoItem = create_item_generator(ExternalImageInfoItem)
createExternalWebsiteItem = create_item_generator(ExternalWebsiteInfoItem)
createEventItem = create_item_generator(EventInfoItem)
+24 -3
View File
@@ -2,14 +2,13 @@ from django.shortcuts import render
from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest
from django.views.decorators.http import require_http_methods
from django.conf import settings
from django.db import DatabaseError
from infoscreen.models import Rotation, InfoItem, InfoInstance
from infoscreen.hsl_fetcher import fetch as hsl_fetch
import json
import logging
import threading
import requests
@require_http_methods(["GET"])
@@ -23,7 +22,7 @@ def default(request, *args, **kwargs):
"""Try getting first rotation item."""
try:
first = Rotation.objects.all()[0].id
except DatabaseError:
except:
first = 0
return index(request, first, *args, **kwargs)
@@ -78,3 +77,25 @@ def info_items(request, *args, **kwargs):
items.append(i.get_dict())
return JsonResponse(items, safe=False)
@require_http_methods(["GET"])
def hsl_timetable_settings(request, *args, **kwargs):
"""Set HSL timetable settings."""
d = {"departure_threshold": settings.HSL_DEPARTURE_THRESHOLD,
"hurry_threshold": settings.HSL_HURRY_THRESHOLD}
return JsonResponse(d, status=200)
@require_http_methods(["GET"])
def CurrentHSLView(request, *args, **kwargs):
"""Get HSL data and return it."""
try:
api_resp = hsl_fetch()
except Exception as ex:
logging.exception('Failed to fetch HSL timetables.')
error = {'error': 'Aikataulujen haku epäonnistui.'}
return JsonResponse(error, status=200)
return JsonResponse(api_resp, status=200, safe=False)
View File

Some files were not shown because too many files have changed in this diff Show More