Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e83b4d4624 | |||
| 38225cabc8 | |||
| 08bb63ce1f | |||
| 72a93e1dfd | |||
| fa5597f7cf | |||
| 92ea427c53 | |||
| abc2519bc7 | |||
| 87a0c68ef2 | |||
| 612b17960a | |||
| 7f21b7bba2 | |||
| 8c116d58de |
@@ -1,11 +1,3 @@
|
|||||||
[report]
|
|
||||||
show_missing = True
|
|
||||||
omit =
|
|
||||||
*/migrations/*
|
|
||||||
*/admin.py
|
|
||||||
*/translation.py
|
|
||||||
[run]
|
[run]
|
||||||
omit =
|
omit =
|
||||||
*/migrations/*
|
*/migrations/*
|
||||||
*/admin.py
|
|
||||||
*/translation.py
|
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
.DS_Store
|
|
||||||
.dockerignore
|
|
||||||
.git/
|
|
||||||
.husky/
|
|
||||||
.venv/
|
|
||||||
.vscode/
|
|
||||||
collected_static/
|
|
||||||
logs/
|
|
||||||
!logs/README
|
|
||||||
media/
|
|
||||||
!media/REMOVE_ME
|
|
||||||
misc/
|
|
||||||
node_modules/
|
|
||||||
scripts/
|
|
||||||
.coverage
|
|
||||||
.coveragerc
|
|
||||||
.env*
|
|
||||||
.eslintignore
|
|
||||||
.eslintrc.json
|
|
||||||
.gitignore
|
|
||||||
.gitlab-ci.yml
|
|
||||||
.python-version
|
|
||||||
docker-compose.yml
|
|
||||||
!manage.py
|
|
||||||
package*.json
|
|
||||||
!poetry.lock
|
|
||||||
!production_entrypoint.sh
|
|
||||||
pycodestyle.cfg
|
|
||||||
!pyproject.toml
|
|
||||||
pyright.json
|
|
||||||
README.md
|
|
||||||
stack-compose*.yml
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
DEPLOY_ENV=local
|
|
||||||
SENTRY_DSN=
|
|
||||||
HOST=api.dev.sahkoinsinoorikilta.fi
|
|
||||||
DEBUG=True
|
|
||||||
SECRET_KEY=7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp(
|
|
||||||
DB_NAME=postgres
|
|
||||||
DB_USER=postgres
|
|
||||||
DB_PASSWD=postgres
|
|
||||||
DB_HOST=db
|
|
||||||
DB_PORT=5432
|
|
||||||
EMAIL_API_KEY=
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
DEPLOY_ENV=local
|
|
||||||
#SENTRY_DSN=
|
|
||||||
HOST=localhost
|
|
||||||
DEBUG=True
|
|
||||||
SECRET_KEY=7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp(
|
|
||||||
DB_NAME=postgres
|
|
||||||
DB_USER=postgres
|
|
||||||
DB_PASSWD=postgres
|
|
||||||
DB_HOST=db
|
|
||||||
DB_PORT=5432
|
|
||||||
EMAIL_API_KEY=
|
|
||||||
GROUP_KEY=
|
|
||||||
GOOGLE_CREDS_JSON='{}'
|
|
||||||
@@ -3,4 +3,3 @@ infoscreen/static/js/lib
|
|||||||
webapp/static/js/lib
|
webapp/static/js/lib
|
||||||
static/js/lib
|
static/js/lib
|
||||||
collected_static
|
collected_static
|
||||||
venv
|
|
||||||
|
|||||||
@@ -4,10 +4,11 @@
|
|||||||
"jquery": true
|
"jquery": true
|
||||||
},
|
},
|
||||||
"globals": {
|
"globals": {
|
||||||
"angular": true,
|
"angular": 1,
|
||||||
"noty": true,
|
"noty": 1,
|
||||||
"_": true,
|
"app": 1,
|
||||||
"moment": true
|
"_": 1,
|
||||||
|
"moment": 1
|
||||||
},
|
},
|
||||||
"extends": "eslint:recommended",
|
"extends": "eslint:recommended",
|
||||||
"rules": {
|
"rules": {
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
.DS_Store
|
*.swp
|
||||||
.env
|
sikweb/settings.py
|
||||||
|
*~
|
||||||
*.pyc
|
*.pyc
|
||||||
/collected_static/
|
*.sqlite3
|
||||||
/media/
|
uwsgi.ini
|
||||||
logs/
|
uwsgi.log
|
||||||
|
infoscreen/static/js/hsl.json
|
||||||
members/logs/*
|
members/logs/*
|
||||||
node_modules/
|
|
||||||
.coverage
|
|
||||||
.vscode/
|
|
||||||
.idea/
|
.idea/
|
||||||
*.code-workspace
|
logs/
|
||||||
venv/
|
/media/
|
||||||
.venv/
|
node_modules/
|
||||||
|
/.coverage
|
||||||
|
db.sqlite3
|
||||||
|
requirements_henu.txt
|
||||||
|
/collected_static/
|
||||||
|
mydatabase
|
||||||
|
settings.json
|
||||||
|
.vscode/
|
||||||
@@ -1,131 +1,100 @@
|
|||||||
stages:
|
stages:
|
||||||
- setup
|
|
||||||
- audit
|
|
||||||
- lint
|
|
||||||
- test
|
- test
|
||||||
|
- lint
|
||||||
- publish
|
- publish
|
||||||
- deploy
|
- deploy
|
||||||
|
|
||||||
install:
|
|
||||||
image: node:14
|
|
||||||
stage: setup
|
|
||||||
script:
|
|
||||||
- npm ci
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- node_modules
|
|
||||||
expire_in: 1 week
|
|
||||||
|
|
||||||
audit:
|
|
||||||
image: python:3.9
|
|
||||||
stage: audit
|
|
||||||
needs: []
|
|
||||||
before_script:
|
|
||||||
- pip install poetry==1.1.13
|
|
||||||
- poetry config virtualenvs.create false
|
|
||||||
- poetry install --no-interaction --no-ansi
|
|
||||||
script:
|
|
||||||
- safety check
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
image: python:3.9
|
image: python:3.5
|
||||||
stage: test
|
stage: test
|
||||||
needs: []
|
|
||||||
services:
|
services:
|
||||||
- postgres:12
|
- postgres:latest
|
||||||
variables:
|
variables:
|
||||||
POSTGRES_DB: ci
|
POSTGRES_DB: ci
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
|
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
|
||||||
DB_HOST: postgres
|
|
||||||
before_script:
|
|
||||||
- pip install poetry==1.1.13
|
|
||||||
- poetry config virtualenvs.create false
|
|
||||||
- poetry install --no-interaction --no-ansi
|
|
||||||
script:
|
script:
|
||||||
|
- python -V
|
||||||
|
- pip install -r requirements.txt
|
||||||
|
- cp sikweb/settings-sample.py sikweb/default_settings.py
|
||||||
|
- cp sikweb/.ci-settings.py sikweb/settings.py
|
||||||
- python manage.py migrate --noinput
|
- python manage.py migrate --noinput
|
||||||
- python manage.py createdefaultadmin
|
- python manage.py createdefaultadmin
|
||||||
- python manage.py test
|
- python manage.py test
|
||||||
|
|
||||||
lint:py:
|
pycodestyle:
|
||||||
image: python:3.9
|
image: python:3.5
|
||||||
stage: lint
|
stage: lint
|
||||||
needs: []
|
|
||||||
script:
|
script:
|
||||||
- pip install black==22.3.0
|
- pip install pycodestyle
|
||||||
- black --check .
|
- pycodestyle --config=setup.cfg --count .
|
||||||
|
|
||||||
lint:js:
|
eslint:
|
||||||
image: node:14
|
image: node:7.10.0
|
||||||
stage: lint
|
stage: lint
|
||||||
needs: ["install"]
|
before_script:
|
||||||
|
- npm install
|
||||||
script:
|
script:
|
||||||
- npm run lint:js
|
- npm run eslint
|
||||||
|
|
||||||
lint:md:
|
remark:
|
||||||
image: node:14
|
image: node:7.10.0
|
||||||
stage: lint
|
stage: lint
|
||||||
needs: ["install"]
|
before_script:
|
||||||
|
- npm install
|
||||||
script:
|
script:
|
||||||
- npm run lint:md
|
- npm run remark
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
stage: publish
|
stage: publish
|
||||||
image: docker:stable
|
image: docker:latest
|
||||||
needs: ["test", "lint:py", "lint:js", "lint:md"]
|
|
||||||
services:
|
|
||||||
- docker:stable-dind
|
|
||||||
only:
|
only:
|
||||||
- develop
|
- develop
|
||||||
- master
|
before_script:
|
||||||
script:
|
|
||||||
- docker info
|
- 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 build . -t "$IMAGE_NAME"
|
||||||
- docker push "$IMAGE_NAME"
|
- docker push "$IMAGE_NAME"
|
||||||
|
|
||||||
deploy:dev:
|
deploy_dev:
|
||||||
stage: deploy
|
stage: deploy
|
||||||
image: docker:stable
|
image: alpine:latest
|
||||||
only:
|
|
||||||
- develop
|
|
||||||
environment:
|
environment:
|
||||||
name: dev
|
name: dev
|
||||||
url: http://api.dev.sahkoinsinoorikilta.fi
|
url: http://web.sik.party:8080
|
||||||
variables:
|
|
||||||
DOCKER_HOST: $DEV_CI_DOCKER_HOST
|
|
||||||
DOCKER_TLS_VERIFY: 1
|
|
||||||
before_script:
|
|
||||||
- mkdir -p ~/.docker
|
|
||||||
- echo "$DEV_TLSCACERT" > ~/.docker/ca.pem
|
|
||||||
- echo "$DEV_TLSCERT" > ~/.docker/cert.pem
|
|
||||||
- echo "$DEV_TLSKEY" > ~/.docker/key.pem
|
|
||||||
- docker login -u gitlab-ci-token -p "$CI_BUILD_TOKEN" "$CI_REGISTRY"
|
|
||||||
script:
|
|
||||||
- docker stack deploy --with-registry-auth -c stack-compose-dev.yml "$SERVICE_NAME"
|
|
||||||
after_script:
|
|
||||||
- docker logout "$CI_REGISTRY"
|
|
||||||
|
|
||||||
deploy:production:
|
|
||||||
stage: deploy
|
|
||||||
image: docker:stable
|
|
||||||
only:
|
only:
|
||||||
- master
|
- develop
|
||||||
|
before_script:
|
||||||
|
- pwd
|
||||||
|
- apk add --update openssh
|
||||||
|
- ssh -V
|
||||||
|
- mkdir -p ~/.ssh
|
||||||
|
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
|
||||||
|
- chmod 600 ~/.ssh/id_rsa
|
||||||
|
- '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
|
||||||
|
script:
|
||||||
|
- scp docker-compose.yml $DEV_SSH_USER@$DEV_SSH_HOST:~/deployment/docker-compose.yml
|
||||||
|
- 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
|
||||||
|
image: alpine:latest
|
||||||
environment:
|
environment:
|
||||||
name: production
|
name: production
|
||||||
url: https://api.sahkoinsinoorikilta.fi
|
url: https://sika.sahkoinsinoorikilta.fi
|
||||||
when: manual
|
when: manual
|
||||||
variables:
|
only:
|
||||||
DOCKER_HOST: $CI_DOCKER_HOST
|
- master
|
||||||
DOCKER_TLS_VERIFY: 1
|
|
||||||
before_script:
|
before_script:
|
||||||
- mkdir -p ~/.docker
|
- pwd
|
||||||
- echo "$TLSCACERT" > ~/.docker/ca.pem
|
- apk add --update openssh
|
||||||
- echo "$TLSCERT" > ~/.docker/cert.pem
|
- ssh -V
|
||||||
- echo "$TLSKEY" > ~/.docker/key.pem
|
- mkdir -p ~/.ssh
|
||||||
- docker login -u gitlab-ci-token -p "$CI_BUILD_TOKEN" "$CI_REGISTRY"
|
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
|
||||||
|
- chmod 600 ~/.ssh/id_rsa
|
||||||
|
- echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
|
||||||
script:
|
script:
|
||||||
- docker stack deploy --with-registry-auth -c stack-compose.yml "$SERVICE_NAME"
|
- ssh $PROD_SSH_USER@$PROD_SSH_HOST "zsh ~/deploy.sh"
|
||||||
after_script:
|
|
||||||
- docker logout "$CI_REGISTRY"
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
_
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
. "$(dirname "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
PURPLE='\033[0;35m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
source "${VIRTUAL_ENV}/bin/activate"
|
|
||||||
|
|
||||||
if [ $? -ne 0 ]
|
|
||||||
then
|
|
||||||
printf "${PURPLE}Failed to find virtualenv. Skipping pre-commit hook.\n${NC}"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
npm run lint
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
. "$(dirname "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
PURPLE='\033[0;35m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
source "${VIRTUAL_ENV}/bin/activate"
|
|
||||||
|
|
||||||
if [ $? -ne 0 ]
|
|
||||||
then
|
|
||||||
printf "${PURPLE}Failed to find virtualenv. Skipping pre-push hook.\n${NC}"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
printf "${PURPLE}Running pre-push tests.${NC}\n"
|
|
||||||
printf "${PURPLE}linters...${NC}\n"
|
|
||||||
npm run lint
|
|
||||||
printf "${PURPLE}unit tests...${NC}\n"
|
|
||||||
python -Wall manage.py test --noinput
|
|
||||||
set +e
|
|
||||||
|
|
||||||
printf "${PURPLE}Tests passed.${NC}\n"
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
3.9
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
global_static
|
||||||
@@ -1,28 +1,8 @@
|
|||||||
FROM python:3.9-slim-buster as builder
|
FROM python:3
|
||||||
ENV PYTHONUNBUFFERED 1
|
ENV PYTHONUNBUFFERED 1
|
||||||
COPY . ./
|
ENV IS_DOCKER 1
|
||||||
|
RUN mkdir /code
|
||||||
ENV POETRY_VERSION=1.1.13
|
WORKDIR /code
|
||||||
|
ADD requirements.txt /code/
|
||||||
RUN pip install "poetry==$POETRY_VERSION"
|
RUN env
|
||||||
RUN poetry export --without-hashes > requirements.txt
|
ADD . /code/
|
||||||
|
|
||||||
FROM python:3.9-slim-buster as server
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
COPY . ./
|
|
||||||
COPY --from=builder requirements.txt ./
|
|
||||||
|
|
||||||
ENV PYTHONUNBUFFERED=1 \
|
|
||||||
# prevents python creating .pyc files
|
|
||||||
PYTHONDONTWRITEBYTECODE=1 \
|
|
||||||
# pip
|
|
||||||
PIP_NO_CACHE_DIR=off \
|
|
||||||
PIP_DISABLE_PIP_VERSION_CHECK=on \
|
|
||||||
PIP_DEFAULT_TIMEOUT=100
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install --no-install-recommends -y build-essential
|
|
||||||
RUN pip install --no-deps -r requirements.txt
|
|
||||||
|
|
||||||
RUN python manage.py collectstatic --noinput
|
|
||||||
CMD ["sh", "-c", "./production_entrypoint.sh"]
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
@@ -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))
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
body {
|
||||||
|
background-color: white;
|
||||||
|
font-family: monospace;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
#container{
|
||||||
|
position:relative;
|
||||||
|
width:95%;
|
||||||
|
margin-left:auto;
|
||||||
|
margin-right:auto;
|
||||||
|
height:100%;
|
||||||
|
overflow:hidden;
|
||||||
|
|
||||||
|
}
|
||||||
|
#upper{
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: bottom center;
|
||||||
|
background-image: url("/static/img/smokes.png");
|
||||||
|
transform-origin: bottom;
|
||||||
|
animation: smokes 8s ease-in-out 0s infinite;
|
||||||
|
opacity:0;
|
||||||
|
height:40%;
|
||||||
|
}
|
||||||
|
#lower{
|
||||||
|
position:relative;
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: top center;
|
||||||
|
background-image: url("/static/img/coffeecup3.png");
|
||||||
|
height:60%;
|
||||||
|
}
|
||||||
|
#scale{
|
||||||
|
position:absolute;
|
||||||
|
top:80%;
|
||||||
|
width:90%;
|
||||||
|
height:10%;
|
||||||
|
margin: 0% 5% 0% 5%;
|
||||||
|
background: lightgrey;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow:hidden;
|
||||||
|
}
|
||||||
|
#scale2{
|
||||||
|
width: 0%;
|
||||||
|
transition: width 2s;
|
||||||
|
height:100%;
|
||||||
|
background: green;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
#brewtime{
|
||||||
|
text-align:right;
|
||||||
|
position:absolute;
|
||||||
|
right:0px;
|
||||||
|
z-index:5;
|
||||||
|
font-size:10vw;
|
||||||
|
}
|
||||||
|
#address{
|
||||||
|
text-align:left;
|
||||||
|
position:absolute;
|
||||||
|
left:0px;
|
||||||
|
z-index:5;
|
||||||
|
font-size:4vw;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
noscript{
|
||||||
|
color:red;
|
||||||
|
}
|
||||||
|
#text{
|
||||||
|
color:green;
|
||||||
|
position:absolute;
|
||||||
|
top:50%;
|
||||||
|
left:50%;
|
||||||
|
}
|
||||||
|
.brewing{
|
||||||
|
animation: brewing 5s ease-in-out 0s infinite;
|
||||||
|
}
|
||||||
|
.hurry{
|
||||||
|
color:red !important;
|
||||||
|
}
|
||||||
|
.unknown{
|
||||||
|
color:orange !important;
|
||||||
|
animation: unknown 5s ease-in-out 0s infinite;
|
||||||
|
}
|
||||||
|
.friday{
|
||||||
|
animation: friday 20s ease-in-out 0s infinite;
|
||||||
|
}
|
||||||
|
.normal{
|
||||||
|
animation: normal 1000s ease-in-out 0s infinite;
|
||||||
|
}
|
||||||
|
.coffeeready{
|
||||||
|
animation: coffeeready 10s ease-in-out 0s;
|
||||||
|
}
|
||||||
|
@keyframes smokes {
|
||||||
|
0% {transform: skewX(-10deg);}
|
||||||
|
50% {transform: skewX(10deg);}
|
||||||
|
100% {transform: skewX(-10deg);}
|
||||||
|
}
|
||||||
|
@keyframes brewing {
|
||||||
|
0% {color:green;}
|
||||||
|
50% {color: transparent;}
|
||||||
|
100% {color:green;}
|
||||||
|
}
|
||||||
|
@keyframes coffeeready {
|
||||||
|
0% {background-color:white;}
|
||||||
|
25% {background-color:green;}
|
||||||
|
50% {background-color:white;}
|
||||||
|
75% {background-color:green;}
|
||||||
|
100% {background-color:white;}
|
||||||
|
}
|
||||||
|
@keyframes unknown {
|
||||||
|
0%,40% {transform: rotate(0deg);}
|
||||||
|
60%,100% {transform: rotate(360deg);}
|
||||||
|
}
|
||||||
|
@keyframes friday {
|
||||||
|
0% {transform: rotate(0deg);}
|
||||||
|
100% {transform: rotate(360deg);}
|
||||||
|
}
|
||||||
|
@keyframes normal {
|
||||||
|
0%,49% {transform: rotate(0deg);}
|
||||||
|
50%,100% {transform: rotate(360deg);}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
@@ -0,0 +1,130 @@
|
|||||||
|
var len = 0;
|
||||||
|
var lastBrew = "∞";
|
||||||
|
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 = "∞";
|
||||||
|
$("#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'});
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Coffee Cups @Guild Room - AYY SIK ry</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="refresh" content="3600">
|
||||||
|
<meta http-equiv="cache-control" content="max-age=0" />
|
||||||
|
<meta http-equiv="cache-control" content="no-cache" />
|
||||||
|
<meta http-equiv="expires" content="0" />
|
||||||
|
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
|
||||||
|
<meta http-equiv="pragma" content="no-cache" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="/static/css/coffee.css" />
|
||||||
|
<script src="/static/js/coffee.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="container">
|
||||||
|
<span id="brewtime"></span>
|
||||||
|
<span id="address">
|
||||||
|
ka.dy.fi
|
||||||
|
<noscript><br>This page uses JavaScript!</noscript>
|
||||||
|
<br><span id="time"></span></span>
|
||||||
|
</span>
|
||||||
|
<div id="upper">
|
||||||
|
</div>
|
||||||
|
<!--Kahvinkeitin on rikki. Varakeittimellä keitettyä kahvia saattaa olla.-->
|
||||||
|
<div id="lower" class="normal">
|
||||||
|
<div id="text"></div>
|
||||||
|
<div id="scale"><div id="scale2"></div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +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)
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
from django.conf.urls import url
|
||||||
|
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),
|
||||||
|
|
||||||
|
]
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
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.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)
|
||||||
@@ -2,23 +2,12 @@ version: '3'
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
image: postgres:12
|
image: postgres
|
||||||
volumes:
|
|
||||||
- dbdata:/var/lib/postgresql/data
|
|
||||||
ports:
|
|
||||||
- "5432:5432"
|
|
||||||
environment:
|
|
||||||
- POSTGRES_PASSWORD=postgres
|
|
||||||
|
|
||||||
web:
|
web:
|
||||||
build: .
|
build: .
|
||||||
image: registry.gitlab.com/sahkoinsinoorikilta/vtmk/web2.0-backend
|
image: 86.50.143.82:5000/web20
|
||||||
env_file:
|
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"]
|
||||||
- .env
|
|
||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "8080:8080"
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
|
|
||||||
volumes:
|
|
||||||
dbdata:
|
|
||||||
|
|||||||
@@ -1,16 +1,10 @@
|
|||||||
"""Admin site registers."""
|
"""Admin site registers."""
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from infoscreen.models import (
|
from infoscreen.models import Rotation, InfoItem, InfoInstance
|
||||||
Rotation,
|
from infoscreen.models import ImageInfoItem, ExternalImageInfoItem, ABBInfoItem
|
||||||
InfoItem,
|
from infoscreen.models import ExternalWebsiteInfoItem
|
||||||
InfoInstance,
|
from infoscreen.models import VideoInfoItem
|
||||||
ImageInfoItem,
|
|
||||||
ExternalImageInfoItem,
|
|
||||||
ABBInfoItem,
|
|
||||||
ExternalWebsiteInfoItem,
|
|
||||||
VideoInfoItem,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Register your models here.
|
# Register your models here.
|
||||||
admin.site.register(Rotation)
|
admin.site.register(Rotation)
|
||||||
|
|||||||
@@ -6,4 +6,4 @@ from django.apps import AppConfig
|
|||||||
class InfoscreenConfig(AppConfig):
|
class InfoscreenConfig(AppConfig):
|
||||||
"""Infoscreen app configuration."""
|
"""Infoscreen app configuration."""
|
||||||
|
|
||||||
name = "infoscreen"
|
name = 'infoscreen'
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"]
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
@@ -11,173 +11,81 @@ class Migration(migrations.Migration):
|
|||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("contenttypes", "0002_remove_content_type_name"),
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name="HSLDataModel",
|
name='HSLDataModel',
|
||||||
fields=[
|
fields=[
|
||||||
(
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
"id",
|
('data', models.TextField(default='', editable=False)),
|
||||||
models.AutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("data", models.TextField(default="", editable=False)),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name="InfoInstance",
|
name='InfoInstance',
|
||||||
fields=[
|
fields=[
|
||||||
(
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
"id",
|
('duration', models.FloatField(default=15.0)),
|
||||||
models.AutoField(
|
('item_id', models.PositiveIntegerField()),
|
||||||
auto_created=True,
|
('item_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("duration", models.FloatField(default=15.0)),
|
|
||||||
("item_id", models.PositiveIntegerField()),
|
|
||||||
(
|
|
||||||
"item_type",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
to="contenttypes.ContentType",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name="InfoItem",
|
name='InfoItem',
|
||||||
fields=[
|
fields=[
|
||||||
(
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
"id",
|
('name', models.CharField(max_length=255)),
|
||||||
models.AutoField(
|
('expire_date', models.DateTimeField(blank=True, null=True)),
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("name", models.CharField(max_length=255)),
|
|
||||||
("expire_date", models.DateTimeField(blank=True, null=True)),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name="Rotation",
|
name='Rotation',
|
||||||
fields=[
|
fields=[
|
||||||
(
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
"id",
|
('name', models.CharField(max_length=255)),
|
||||||
models.AutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("name", models.CharField(max_length=255)),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name="ABBInfoItem",
|
name='ABBInfoItem',
|
||||||
fields=[
|
fields=[
|
||||||
(
|
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||||
"infoitem_ptr",
|
|
||||||
models.OneToOneField(
|
|
||||||
auto_created=True,
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
parent_link=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
to="infoscreen.InfoItem",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
bases=("infoscreen.infoitem",),
|
bases=('infoscreen.infoitem',),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name="ExternalImageInfoItem",
|
name='ExternalImageInfoItem',
|
||||||
fields=[
|
fields=[
|
||||||
(
|
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||||
"infoitem_ptr",
|
('url', models.TextField()),
|
||||||
models.OneToOneField(
|
|
||||||
auto_created=True,
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
parent_link=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
to="infoscreen.InfoItem",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("url", models.TextField()),
|
|
||||||
],
|
],
|
||||||
bases=("infoscreen.infoitem",),
|
bases=('infoscreen.infoitem',),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name="HslInfoItem",
|
name='HslInfoItem',
|
||||||
fields=[
|
fields=[
|
||||||
(
|
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||||
"infoitem_ptr",
|
|
||||||
models.OneToOneField(
|
|
||||||
auto_created=True,
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
parent_link=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
to="infoscreen.InfoItem",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
bases=("infoscreen.infoitem",),
|
bases=('infoscreen.infoitem',),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name="ImageInfoItem",
|
name='ImageInfoItem',
|
||||||
fields=[
|
fields=[
|
||||||
(
|
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||||
"infoitem_ptr",
|
('img', models.ImageField(upload_to='infoimages/')),
|
||||||
models.OneToOneField(
|
|
||||||
auto_created=True,
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
parent_link=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
to="infoscreen.InfoItem",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("img", models.ImageField(upload_to="infoimages/")),
|
|
||||||
],
|
],
|
||||||
bases=("infoscreen.infoitem",),
|
bases=('infoscreen.infoitem',),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name="SossoInfoItem",
|
name='SossoInfoItem',
|
||||||
fields=[
|
fields=[
|
||||||
(
|
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||||
"infoitem_ptr",
|
|
||||||
models.OneToOneField(
|
|
||||||
auto_created=True,
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
parent_link=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
to="infoscreen.InfoItem",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
bases=("infoscreen.infoitem",),
|
bases=('infoscreen.infoitem',),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name="infoinstance",
|
model_name='infoinstance',
|
||||||
name="rotation",
|
name='rotation',
|
||||||
field=models.ForeignKey(
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='instances', to='infoscreen.Rotation'),
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
related_name="instances",
|
|
||||||
to="infoscreen.Rotation",
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -9,25 +9,15 @@ import django.db.models.deletion
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("infoscreen", "0001_initial"),
|
('infoscreen', '0001_initial'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name="CoffeeInfoItem",
|
name='CoffeeInfoItem',
|
||||||
fields=[
|
fields=[
|
||||||
(
|
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||||
"infoitem_ptr",
|
|
||||||
models.OneToOneField(
|
|
||||||
auto_created=True,
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
parent_link=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
to="infoscreen.InfoItem",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
bases=("infoscreen.infoitem",),
|
bases=('infoscreen.infoitem',),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -9,63 +9,33 @@ import django.db.models.deletion
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("infoscreen", "0002_coffeeinfoitem"),
|
('infoscreen', '0002_coffeeinfoitem'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name="ApyInfoItem",
|
name='ApyInfoItem',
|
||||||
fields=[
|
fields=[
|
||||||
(
|
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||||
"infoitem_ptr",
|
|
||||||
models.OneToOneField(
|
|
||||||
auto_created=True,
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
parent_link=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
to="infoscreen.InfoItem",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
bases=("infoscreen.infoitem",),
|
bases=('infoscreen.infoitem',),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name="EventInfoItem",
|
name='EventInfoItem',
|
||||||
fields=[
|
fields=[
|
||||||
(
|
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||||
"infoitem_ptr",
|
|
||||||
models.OneToOneField(
|
|
||||||
auto_created=True,
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
parent_link=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
to="infoscreen.InfoItem",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
bases=("infoscreen.infoitem",),
|
bases=('infoscreen.infoitem',),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name="ExternalWebsiteInfoItem",
|
name='ExternalWebsiteInfoItem',
|
||||||
fields=[
|
fields=[
|
||||||
(
|
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||||
"infoitem_ptr",
|
('url', models.TextField()),
|
||||||
models.OneToOneField(
|
|
||||||
auto_created=True,
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
parent_link=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
to="infoscreen.InfoItem",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("url", models.TextField()),
|
|
||||||
],
|
],
|
||||||
bases=("infoscreen.infoitem",),
|
bases=('infoscreen.infoitem',),
|
||||||
),
|
),
|
||||||
migrations.DeleteModel(
|
migrations.DeleteModel(
|
||||||
name="CoffeeInfoItem",
|
name='CoffeeInfoItem',
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -9,26 +9,16 @@ import django.db.models.deletion
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("infoscreen", "0003_auto_20170329_1857"),
|
('infoscreen', '0003_auto_20170329_1857'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name="VideoInfoItem",
|
name='VideoInfoItem',
|
||||||
fields=[
|
fields=[
|
||||||
(
|
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||||
"infoitem_ptr",
|
('video', models.FileField(upload_to='infovideos/')),
|
||||||
models.OneToOneField(
|
|
||||||
auto_created=True,
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
parent_link=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
to="infoscreen.InfoItem",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("video", models.FileField(upload_to="infovideos/")),
|
|
||||||
],
|
],
|
||||||
bases=("infoscreen.infoitem",),
|
bases=('infoscreen.infoitem',),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -8,18 +8,18 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("infoscreen", "0004_videoinfoitem"),
|
('infoscreen', '0004_videoinfoitem'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="externalimageinfoitem",
|
model_name='externalimageinfoitem',
|
||||||
name="url",
|
name='url',
|
||||||
field=models.URLField(),
|
field=models.URLField(),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="externalwebsiteinfoitem",
|
model_name='externalwebsiteinfoitem',
|
||||||
name="url",
|
name='url',
|
||||||
field=models.URLField(),
|
field=models.URLField(),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -8,14 +8,11 @@ from django.db import migrations
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("infoscreen", "0005_auto_20170913_1841"),
|
('infoscreen', '0005_auto_20170913_1841'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.DeleteModel(
|
migrations.DeleteModel(
|
||||||
name="HSLDataModel",
|
name='HSLDataModel',
|
||||||
),
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name="HslInfoItem",
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,31 +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",),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
# Generated by Django 3.2.14 on 2022-08-01 19:13
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("infoscreen", "0007_lunchitem"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="infoinstance",
|
|
||||||
name="id",
|
|
||||||
field=models.AutoField(primary_key=True, serialize=False),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="infoitem",
|
|
||||||
name="id",
|
|
||||||
field=models.AutoField(primary_key=True, serialize=False),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="rotation",
|
|
||||||
name="id",
|
|
||||||
field=models.AutoField(primary_key=True, serialize=False),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -16,7 +16,6 @@ class InfoItem(models.Model):
|
|||||||
class __meta__:
|
class __meta__:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
id = models.AutoField(primary_key=True)
|
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
# expire_date = None means never expiring item
|
# expire_date = None means never expiring item
|
||||||
expire_date = models.DateTimeField(blank=True, null=True)
|
expire_date = models.DateTimeField(blank=True, null=True)
|
||||||
@@ -24,14 +23,14 @@ class InfoItem(models.Model):
|
|||||||
|
|
||||||
def get_template_url(self):
|
def get_template_url(self):
|
||||||
"""Get infoscreen template url."""
|
"""Get infoscreen template url."""
|
||||||
raise NotImplementedError("inheriting classes must implement get_template_url")
|
raise NotImplementedError(
|
||||||
|
"inheriting classes must implement get_template_url")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_create_template_url():
|
def get_create_template_url():
|
||||||
"""Get create infoscreen template url command."""
|
"""Get create infoscreen template url command."""
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
"inheriting classes must implement get_create_template_url"
|
"inheriting classes must implement get_create_template_url")
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_from_dict(cls, d):
|
def create_from_dict(cls, d):
|
||||||
@@ -43,13 +42,14 @@ class InfoItem(models.Model):
|
|||||||
def update_from_dict(self, d):
|
def update_from_dict(self, d):
|
||||||
"""Update model based on given dict."""
|
"""Update model based on given dict."""
|
||||||
try:
|
try:
|
||||||
expire_date = d.pop("expire_date", None)
|
expire_date = d.pop('expire_date', None)
|
||||||
self.expire_date = datetime.strptime(expire_date, "%Y-%m-%d %H:%M:%S")
|
self.expire_date = datetime.strptime(
|
||||||
|
expire_date, "%Y-%m-%d %H:%M:%S")
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
dmap = {
|
dmap = {
|
||||||
"name": "name",
|
'name': 'name',
|
||||||
}
|
}
|
||||||
for k, v in d.items():
|
for k, v in d.items():
|
||||||
try:
|
try:
|
||||||
@@ -61,13 +61,13 @@ class InfoItem(models.Model):
|
|||||||
def get_dict(self):
|
def get_dict(self):
|
||||||
"""Convert django model to dict and return it."""
|
"""Convert django model to dict and return it."""
|
||||||
return {
|
return {
|
||||||
"id": self.id,
|
'id': self.id,
|
||||||
"name": self.name,
|
'name': self.name,
|
||||||
"item_type": ContentType.objects.get_for_model(self).id,
|
'item_type': ContentType.objects.get_for_model(self).id,
|
||||||
"template_url": self.get_template_url(),
|
'template_url': self.get_template_url(),
|
||||||
"display_name": self.display_name,
|
'display_name': self.display_name,
|
||||||
"create_template_url": self.get_create_template_url(),
|
'create_template_url': self.get_create_template_url(),
|
||||||
"options": {},
|
'options': {}
|
||||||
}
|
}
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
@@ -75,8 +75,8 @@ class InfoItem(models.Model):
|
|||||||
# since generic foreign keys suck, delete info
|
# since generic foreign keys suck, delete info
|
||||||
# items pointing here manually
|
# items pointing here manually
|
||||||
InfoInstance.objects.filter(
|
InfoInstance.objects.filter(
|
||||||
item_id=self.id, item_type=ContentType.objects.get_for_model(self)
|
item_id=self.id,
|
||||||
).delete()
|
item_type=ContentType.objects.get_for_model(self)).delete()
|
||||||
super().delete()
|
super().delete()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -98,12 +98,12 @@ class ABBInfoItem(InfoItem):
|
|||||||
|
|
||||||
def get_template_url(self):
|
def get_template_url(self):
|
||||||
"""Return ABB infoitem template url."""
|
"""Return ABB infoitem template url."""
|
||||||
return "/static/infoscreen/html/abb.html"
|
return "/static/html/abb.html"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_create_template_url():
|
def get_create_template_url():
|
||||||
"""Call create ABB infoitem template url command."""
|
"""Call create ABB infoitem template url command."""
|
||||||
return "/static/infoscreen/html/abb_create.html"
|
return "/static/html/abb_create.html"
|
||||||
|
|
||||||
|
|
||||||
class ApyInfoItem(InfoItem):
|
class ApyInfoItem(InfoItem):
|
||||||
@@ -113,12 +113,12 @@ class ApyInfoItem(InfoItem):
|
|||||||
|
|
||||||
def get_template_url(self):
|
def get_template_url(self):
|
||||||
"""Return APY infoitem template url."""
|
"""Return APY infoitem template url."""
|
||||||
return "/static/infoscreen/html/apy.html"
|
return "/static/html/apy.html"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_create_template_url():
|
def get_create_template_url():
|
||||||
"""Call create APY infoitem template url command."""
|
"""Call create APY infoitem template url command."""
|
||||||
return "/static/infoscreen/html/apy_create.html"
|
return "/static/html/apy_create.html"
|
||||||
|
|
||||||
|
|
||||||
class ExternalWebsiteInfoItem(InfoItem):
|
class ExternalWebsiteInfoItem(InfoItem):
|
||||||
@@ -129,17 +129,17 @@ class ExternalWebsiteInfoItem(InfoItem):
|
|||||||
|
|
||||||
def get_template_url(self):
|
def get_template_url(self):
|
||||||
"""Return external website infoitem template url."""
|
"""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
|
@staticmethod
|
||||||
def get_create_template_url():
|
def get_create_template_url():
|
||||||
"""Call create external website infoitem template url command."""
|
"""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):
|
def get_dict(self):
|
||||||
"""Convert django model to dict and return it."""
|
"""Convert django model to dict and return it."""
|
||||||
d = super().get_dict()
|
d = super().get_dict()
|
||||||
d["options"] = {"url": self.url}
|
d["options"] = {'url': self.url}
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -152,22 +152,23 @@ class ExternalWebsiteInfoItem(InfoItem):
|
|||||||
def get_list(self):
|
def get_list(self):
|
||||||
"""Return list containing infoitem data."""
|
"""Return list containing infoitem data."""
|
||||||
return {
|
return {
|
||||||
"id": self.id,
|
'id': self.id,
|
||||||
"name": self.name,
|
'name': self.name,
|
||||||
"url": self.url,
|
'url': self.url,
|
||||||
}
|
}
|
||||||
|
|
||||||
def update_from_dict(self, d):
|
def update_from_dict(self, d):
|
||||||
"""Update model based on given dict."""
|
"""Update model based on given dict."""
|
||||||
try:
|
try:
|
||||||
expire_date = d.pop("expire_date", None)
|
expire_date = d.pop('expire_date', None)
|
||||||
self.expire_date = datetime.strptime(expire_date, "%Y-%m-%d %H:%M:%S")
|
self.expire_date = datetime.strptime(
|
||||||
|
expire_date, "%Y-%m-%d %H:%M:%S")
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
dmap = {
|
dmap = {
|
||||||
"name": "name",
|
'name': 'name',
|
||||||
"url": "url",
|
'url': 'url',
|
||||||
}
|
}
|
||||||
for k, v in d.items():
|
for k, v in d.items():
|
||||||
try:
|
try:
|
||||||
@@ -184,25 +185,12 @@ class SossoInfoItem(InfoItem):
|
|||||||
|
|
||||||
def get_template_url(self):
|
def get_template_url(self):
|
||||||
"""Return Sosso infoitem template url."""
|
"""Return Sosso infoitem template url."""
|
||||||
return "/static/infoscreen/html/sosso.html"
|
return "/static/html/sosso.html"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_create_template_url():
|
def get_create_template_url():
|
||||||
"""Call create Sosso infoitem template url command."""
|
"""Call create Sosso infoitem template url command."""
|
||||||
return "/static/infoscreen/html/sosso_create.html"
|
return "/static/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"
|
|
||||||
|
|
||||||
|
|
||||||
class EventInfoItem(InfoItem):
|
class EventInfoItem(InfoItem):
|
||||||
@@ -212,12 +200,12 @@ class EventInfoItem(InfoItem):
|
|||||||
|
|
||||||
def get_template_url(self):
|
def get_template_url(self):
|
||||||
"""Return Event infoitem template url."""
|
"""Return Event infoitem template url."""
|
||||||
return "/static/infoscreen/html/events.html"
|
return "/static/html/events.html"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_create_template_url():
|
def get_create_template_url():
|
||||||
"""Call create Event infoitem template url command."""
|
"""Call create Event infoitem template url command."""
|
||||||
return "/static/infoscreen/html/events_create.html"
|
return "/static/html/events_create.html"
|
||||||
|
|
||||||
|
|
||||||
class ImageInfoItem(InfoItem):
|
class ImageInfoItem(InfoItem):
|
||||||
@@ -230,42 +218,57 @@ class ImageInfoItem(InfoItem):
|
|||||||
"""Return Image infoitem template url."""
|
"""Return Image infoitem template url."""
|
||||||
# get param to avoid angular from optimizing same template
|
# get param to avoid angular from optimizing same template
|
||||||
# with different options
|
# 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
|
@staticmethod
|
||||||
def get_create_template_url():
|
def get_create_template_url():
|
||||||
"""Call create Image infoitem template url command."""
|
"""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):
|
def get_dict(self):
|
||||||
"""Convert django model to dict and return it."""
|
"""Convert django model to dict and return it."""
|
||||||
d = super().get_dict()
|
d = super().get_dict()
|
||||||
d["options"] = {"img": self.img.url}
|
d["options"] = {'img': self.img.url}
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
class VideoInfoItem(InfoItem):
|
class VideoInfoItem(InfoItem):
|
||||||
"""Class for Video Infoscreen item."""
|
"""Class for Video Infoscreen item."""
|
||||||
|
|
||||||
display_name = "Video"
|
display_name = ("Video")
|
||||||
video = models.FileField(upload_to="infovideos/")
|
video = models.FileField(upload_to="infovideos/")
|
||||||
|
|
||||||
def get_template_url(self):
|
def get_template_url(self):
|
||||||
"""Return Video infoitem template url."""
|
"""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
|
@staticmethod
|
||||||
def get_create_template_url():
|
def get_create_template_url():
|
||||||
"""Call create Video infoitem template url command."""
|
"""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):
|
def get_dict(self):
|
||||||
"""Convert django model to dict and return it."""
|
"""Convert django model to dict and return it."""
|
||||||
d = super().get_dict()
|
d = super().get_dict()
|
||||||
d["options"] = {"video": self.video.url}
|
d["options"] = {'video': self.video.url}
|
||||||
return d
|
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 ExternalImageInfoItem(InfoItem):
|
||||||
"""Class for External Image Infoscreen item."""
|
"""Class for External Image Infoscreen item."""
|
||||||
|
|
||||||
@@ -274,17 +277,17 @@ class ExternalImageInfoItem(InfoItem):
|
|||||||
|
|
||||||
def get_template_url(self):
|
def get_template_url(self):
|
||||||
"""Return External Image infoitem template url."""
|
"""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
|
@staticmethod
|
||||||
def get_create_template_url():
|
def get_create_template_url():
|
||||||
"""Call create External Image infoitem template url command."""
|
"""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):
|
def get_dict(self):
|
||||||
"""Convert django model to dict and return it."""
|
"""Convert django model to dict and return it."""
|
||||||
d = super().get_dict()
|
d = super().get_dict()
|
||||||
d["options"] = {"img": self.url}
|
d["options"] = {'img': self.url}
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -297,14 +300,15 @@ class ExternalImageInfoItem(InfoItem):
|
|||||||
def update_from_dict(self, d):
|
def update_from_dict(self, d):
|
||||||
"""Update model based on given dict."""
|
"""Update model based on given dict."""
|
||||||
try:
|
try:
|
||||||
expire_date = d.pop("expire_date", None)
|
expire_date = d.pop('expire_date', None)
|
||||||
self.expire_date = datetime.strptime(expire_date, "%Y-%m-%d %H:%M:%S")
|
self.expire_date = datetime.strptime(
|
||||||
|
expire_date, "%Y-%m-%d %H:%M:%S")
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
dmap = {
|
dmap = {
|
||||||
"name": "name",
|
'name': 'name',
|
||||||
"url": "url",
|
'url': 'url',
|
||||||
}
|
}
|
||||||
for k, v in d.items():
|
for k, v in d.items():
|
||||||
try:
|
try:
|
||||||
@@ -317,15 +321,12 @@ class ExternalImageInfoItem(InfoItem):
|
|||||||
class InfoInstance(models.Model):
|
class InfoInstance(models.Model):
|
||||||
"""Class for Info instance in Infoscreen."""
|
"""Class for Info instance in Infoscreen."""
|
||||||
|
|
||||||
id = models.AutoField(primary_key=True)
|
rotation = models.ForeignKey('Rotation', related_name='instances')
|
||||||
rotation = models.ForeignKey(
|
|
||||||
"Rotation", related_name="instances", on_delete=models.CASCADE
|
|
||||||
)
|
|
||||||
duration = models.FloatField(default=15.0) # seconds
|
duration = models.FloatField(default=15.0) # seconds
|
||||||
# generic relation to some kind of InfoItem
|
# generic relation to some kind of InfoItem
|
||||||
item_id = models.PositiveIntegerField()
|
item_id = models.PositiveIntegerField()
|
||||||
item_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
item_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||||
item = GenericForeignKey("item_type", "item_id")
|
item = GenericForeignKey('item_type', 'item_id')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_from_dict(cls, d):
|
def create_from_dict(cls, d):
|
||||||
@@ -338,27 +339,31 @@ class InfoInstance(models.Model):
|
|||||||
except:
|
except:
|
||||||
raise RuntimeError("invalid parameters supplied supplied")
|
raise RuntimeError("invalid parameters supplied supplied")
|
||||||
try:
|
try:
|
||||||
return cls.objects.create(rotation=rotation, item=item, duration=duration)
|
return cls.objects.create(
|
||||||
|
rotation=rotation,
|
||||||
|
item=item,
|
||||||
|
duration=duration
|
||||||
|
)
|
||||||
except:
|
except:
|
||||||
raise RuntimeError("error while adding instance to db")
|
raise RuntimeError("error while adding instance to db")
|
||||||
|
|
||||||
def get_dict(self):
|
def get_dict(self):
|
||||||
"""Convert django model to dict and return it."""
|
"""Convert django model to dict and return it."""
|
||||||
return {
|
return {
|
||||||
"id": self.id,
|
'id': self.id,
|
||||||
"item": self.item.get_dict(),
|
'item': self.item.get_dict(),
|
||||||
"duration": self.duration,
|
'duration': self.duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""Return model name."""
|
"""Return model name."""
|
||||||
return "{}: {} ({}s)".format(self.rotation.name, self.item.name, self.duration)
|
return "{}: {} ({}s)".format(
|
||||||
|
self.rotation.name, self.item.name, self.duration)
|
||||||
|
|
||||||
|
|
||||||
class Rotation(models.Model):
|
class Rotation(models.Model):
|
||||||
"""Class for rotation model."""
|
"""Class for rotation model."""
|
||||||
|
|
||||||
id = models.AutoField(primary_key=True)
|
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
|
|
||||||
def get_dict(self):
|
def get_dict(self):
|
||||||
@@ -367,20 +372,21 @@ class Rotation(models.Model):
|
|||||||
# to avoid excluding items with no expire_date)
|
# to avoid excluding items with no expire_date)
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
instances = self.instances.all()
|
instances = self.instances.all()
|
||||||
filtered = filter(lambda i: (i.item.expire_date or now) >= now, list(instances))
|
filtered = filter(lambda i: (i.item.expire_date or now) >= now,
|
||||||
|
list(instances))
|
||||||
instance_list = list(map(lambda i: i.get_dict(), filtered))
|
instance_list = list(map(lambda i: i.get_dict(), filtered))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"id": self.id,
|
'id': self.id,
|
||||||
"name": self.name,
|
'name': self.name,
|
||||||
"instances": instance_list,
|
'instances': instance_list,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_list(self):
|
def get_list(self):
|
||||||
"""Return list containing infoitem data."""
|
"""Return list containing infoitem data."""
|
||||||
return {
|
return {
|
||||||
"id": self.id,
|
'id': self.id,
|
||||||
"name": self.name,
|
'name': self.name,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@@ -391,7 +397,6 @@ class Rotation(models.Model):
|
|||||||
class ImageUploadForm(forms.Form):
|
class ImageUploadForm(forms.Form):
|
||||||
"""Form used to handle imageuploads to infoscreen app."""
|
"""Form used to handle imageuploads to infoscreen app."""
|
||||||
|
|
||||||
id = models.AutoField(primary_key=True)
|
|
||||||
name = forms.CharField()
|
name = forms.CharField()
|
||||||
image = forms.ImageField()
|
image = forms.ImageField()
|
||||||
|
|
||||||
@@ -399,6 +404,5 @@ class ImageUploadForm(forms.Form):
|
|||||||
class UploadFileForm(forms.Form):
|
class UploadFileForm(forms.Form):
|
||||||
"""Form used for uploading file."""
|
"""Form used for uploading file."""
|
||||||
|
|
||||||
id = models.AutoField(primary_key=True)
|
|
||||||
name = forms.CharField()
|
name = forms.CharField()
|
||||||
video = forms.FileField()
|
video = forms.FileField()
|
||||||
|
|||||||
@@ -2,10 +2,7 @@ body {
|
|||||||
font-family: 'Open Sans', sans-serif;
|
font-family: 'Open Sans', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
#header:after {
|
#header {
|
||||||
content: " ";
|
|
||||||
display: block;
|
|
||||||
clear: both;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#header-logo {
|
#header-logo {
|
||||||
@@ -12,7 +12,7 @@ body {
|
|||||||
.event {
|
.event {
|
||||||
font-size: 100px;
|
font-size: 100px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
}
|
}
|
||||||
.event-col{
|
.event-col{
|
||||||
padding-top:1vh;
|
padding-top:1vh;
|
||||||
@@ -21,7 +21,7 @@ body {
|
|||||||
|
|
||||||
.header-row{
|
.header-row{
|
||||||
margin: 30px;
|
margin: 30px;
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
font-size: 130px;
|
font-size: 130px;
|
||||||
padding-bottom:20px;
|
padding-bottom:20px;
|
||||||
color:#24a05f;
|
color:#24a05f;
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
#infocontent {
|
#infocontent {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
z-index: -1; /* Ensure div tag stays behind content; -999 might work, too. */
|
z-index: -1; /* Ensure div tag stays behind content; -999 might work, too. */
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,6 @@ html {
|
|||||||
body {
|
body {
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
margin: 0.5rem;
|
margin: 0.5rem;
|
||||||
height: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tbody {
|
tbody {
|
||||||
@@ -50,18 +49,7 @@ td {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#header {
|
#header {
|
||||||
max-width: 100%;
|
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 {
|
.article-thumb-col {
|
||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
text-align: left;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.article-title-col {
|
.article-title-col {
|
||||||
@@ -31,10 +31,10 @@
|
|||||||
max-height 200px;
|
max-height 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sossoimage {
|
#sossoimage {
|
||||||
height:300px;
|
height:300px;
|
||||||
position: relative;
|
position: relative;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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">
|
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
|
||||||
<div ng-controller="ABBController">
|
<div ng-controller="ABBController">
|
||||||
<!-- Only show the job listing if there are any jobs, i.e, the jobs list is non-empty -->
|
<!-- 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" class="row" ng-if="jobs.length > 0">
|
||||||
<div id="header-logo">
|
<div id="header-logo">
|
||||||
<img src="/static/infoscreen/img/ABB_logo.png">
|
<img src="/static/img/ABB_logo.png">
|
||||||
</div>
|
</div>
|
||||||
<div id="header-title">
|
<div id="header-title">
|
||||||
TYÖPAIKAT
|
TYÖPAIKAT
|
||||||
@@ -28,6 +28,6 @@
|
|||||||
|
|
||||||
<!-- If there are no jobs, show a static image -->
|
<!-- If there are no jobs, show a static image -->
|
||||||
<div class="row" ng-if="jobs.length == 0">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Name:</label>
|
<label>Name:</label>
|
||||||
<input class="form-control" type="text" ng-model="item.name"></input>
|
<input type="text" ng-model="item.name"></input>
|
||||||
</div>
|
</div>
|
||||||
<input type="button" class="btn btn-success" ng-click="send()" value="create"></input>
|
<input type="button" class="btn btn-success" ng-click="send()" value="create"></input>
|
||||||
</div>
|
</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">
|
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro" rel="stylesheet">
|
||||||
<div id="bg">
|
<div id="bg">
|
||||||
<div class="container " ng-controller="ApyController">
|
<div class="container " ng-controller="ApyController">
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
<div ng-controller="infoadmin_apyitem_create" style="margin-top:20px;">
|
<div ng-controller="infoadmin_apyitem_create" style="margin-top:20px;">
|
||||||
<div>
|
<div>
|
||||||
Create new ÄPY statistics item
|
create apyitem
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Name:</label>
|
<label>Name:</label>
|
||||||
<input class="form-control" type="text" ng-model="item.name"></input>
|
<input type="text" ng-model="item.name"></input>
|
||||||
</div>
|
</div>
|
||||||
<input type="button" class="btn btn-success" ng-click="send()" value="create"></input>
|
<input type="button" class="btn btn-success" ng-click="send()" value="create"></input>
|
||||||
</div>
|
</div>
|
||||||
@@ -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,5 +1,5 @@
|
|||||||
<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">
|
<link href="https://fonts.googleapis.com/css?family=Droid+Sans+Mono" rel="stylesheet">
|
||||||
<div class="container" ng-app="myApp" ng-controller="EventController">
|
<div class="container" ng-app="myApp" ng-controller="EventController">
|
||||||
<div class="header-row row">
|
<div class="header-row row">
|
||||||
<div class="col-sm-6">Tapahtuma</div>
|
<div class="col-sm-6">Tapahtuma</div>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Name:</label>
|
<label>Name:</label>
|
||||||
<input type="text" class="form-control" ng-model="item.name"></input>
|
<input type="text" ng-model="item.name"></input>
|
||||||
</div>
|
</div>
|
||||||
<input type="button" class="btn btn-success" ng-click="send()" value="create"></input>
|
<input type="button" class="btn btn-success" ng-click="send()" value="create"></input>
|
||||||
</div>
|
</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">
|
<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>
|
<p>Your browser does not support iframes.</p>
|
||||||
</iframe>
|
</iframe>
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
<div ng-controller="infoadmin_websiteitem_create" style="margin-top:20px;">
|
<div ng-controller="infoadmin_websiteitem_create" style="margin-top:20px;">
|
||||||
<div>
|
<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>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Name:</label>
|
<label>Name:</label>
|
||||||
<input type="text" class="form-control" ng-model="item.name"></input>
|
<input type="text" ng-model="item.name"></input>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Url:</label>
|
<label>Url:</label>
|
||||||
<input type="text" class="form-control" ng-model="item.url"></input>
|
<input type="text" ng-model="item.url"></input>
|
||||||
</div>
|
</div>
|
||||||
<input type="button" class="btn btn-success" ng-click="send()" value="create"></input>
|
<input type="button" class="btn btn-success" ng-click="send()" value="create"></input>
|
||||||
</div>
|
</div>
|
||||||
@@ -4,11 +4,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Name:</label>
|
<label>Name:</label>
|
||||||
<input type="text" class="form-control" ng-model="item.name"></input>
|
<input type="text" ng-model="item.name"></input>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Url:</label>
|
<label>Url:</label>
|
||||||
<input type="text" class="form-control" ng-model="item.url"></input>
|
<input type="text" ng-model="item.url"></input>
|
||||||
</div>
|
</div>
|
||||||
<input type="button" class="btn btn-success" ng-click="send()" value="create"></input>
|
<input type="button" class="btn btn-success" ng-click="send()" value="create"></input>
|
||||||
</div>
|
</div>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Name:</label>
|
<label>Name:</label>
|
||||||
<input type="text" class="form-control" ng-model="imagename"></input>
|
<input type="text" ng-model="imagename"></input>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="file" ngf-select ng-model="img" name="file" required>
|
<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">
|
<div class="fullscreen-bg">
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Name:</label>
|
<label>Name:</label>
|
||||||
<input type="text" class="form-control" ng-model="name"></input>
|
<input type="text" ng-model="name"></input>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="file" ngf-select ng-model="video" name="file" required>
|
<input type="file" ngf-select ng-model="video" name="file" required>
|
||||||
@@ -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ä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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Name:</label>
|
<label>Name:</label>
|
||||||
<input type="text" class="form-control" ng-model="item.name"></input>
|
<input type="text" ng-model="item.name"></input>
|
||||||
</div>
|
</div>
|
||||||
<input type="button" class="btn btn-success" ng-click="send()" value="create"></input>
|
<input type="button" class="btn btn-success" ng-click="send()" value="create"></input>
|
||||||
</div>
|
</div>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<h1>testi2</h1>
|
||||||
@@ -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 |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 163 KiB |
|
After Width: | Height: | Size: 736 KiB |
|
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 |
|
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",
|
"external_image",
|
||||||
"abbitem",
|
"abbitem",
|
||||||
"sossoitem",
|
"sossoitem",
|
||||||
"lunchitem",
|
|
||||||
"eventitem",
|
"eventitem",
|
||||||
|
"hslitem",
|
||||||
"websiteitem",
|
"websiteitem",
|
||||||
"apyitem",
|
"apyitem",
|
||||||
];
|
];
|
||||||
@@ -46,18 +46,10 @@ 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){
|
app.controller('ABBController', function($scope, $http){
|
||||||
$scope.jobs = [];
|
$scope.jobs = [];
|
||||||
var min_date = moment().subtract(30,'days').format("YYYY-MM-DD%20HH:mm:ss");
|
var min_date = moment().subtract(30,'days').format("YYYY-MM-DD%20HH:mm:ss");
|
||||||
// TODO: FIX, we try to get rid of php, not depend on it!
|
var url = "https://sahkoinsinoorikilta.fi/api/news.php";
|
||||||
var url = "https://old.sahkoinsinoorikilta.fi/api/news.php";
|
|
||||||
var params = "?type=11&lang=fi&title_search=ABB&min_date="+min_date
|
var params = "?type=11&lang=fi&title_search=ABB&min_date="+min_date
|
||||||
$http.get(url+params).then(function(response){
|
$http.get(url+params).then(function(response){
|
||||||
$scope.jobs = _.filter(response.data, function(job){
|
$scope.jobs = _.filter(response.data, function(job){
|
||||||
@@ -83,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) {
|
app.controller('ApyController', function($scope, $http) {
|
||||||
$scope.items = [];
|
$scope.items = [];
|
||||||
$http.get("/infoscreen/apyjson").then(function(response)
|
$http.get("/infoscreen/apyjson").then(function(response)
|
||||||
@@ -135,3 +112,35 @@ app.filter('unixTimeToDifference', function() {
|
|||||||
return res;
|
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();
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
{% 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>
|
||||||
|
|
||||||
|
<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 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>
|
||||||
|
|
||||||
|
</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>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{% load static %}
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html ng-app="infoApp">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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>
|
||||||
@@ -35,6 +35,6 @@ class InfoscreenTestCase(TestCase):
|
|||||||
That would mean that something meaningful has been included
|
That would mean that something meaningful has been included
|
||||||
in the response.
|
in the response.
|
||||||
"""
|
"""
|
||||||
resp = self.c.get("/infoscreen/items")
|
resp = self.c.get('/infoscreen/items')
|
||||||
content = resp.json()
|
content = resp.json()
|
||||||
self.assertTrue(len(content) > 0)
|
self.assertTrue(len(content) > 0)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
"""File containing infoscreen urls."""
|
"""File containing infoscreen urls."""
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from infoscreen.views import index
|
from infoscreen.views import index
|
||||||
from infoscreen.views import admin
|
from infoscreen.views import admin
|
||||||
@@ -18,40 +17,40 @@ from infoscreen.views import create_image_item
|
|||||||
from infoscreen.views import create_video_item
|
from infoscreen.views import create_video_item
|
||||||
from infoscreen.views import createABBItem
|
from infoscreen.views import createABBItem
|
||||||
from infoscreen.views import createSossoItem
|
from infoscreen.views import createSossoItem
|
||||||
from infoscreen.views import createLunchItem
|
from infoscreen.views import createHslItem
|
||||||
from infoscreen.views import createEventItem
|
from infoscreen.views import createEventItem
|
||||||
from infoscreen.views import createExternalWebsiteItem
|
from infoscreen.views import createExternalWebsiteItem
|
||||||
from infoscreen.views import create_rotation
|
from infoscreen.views import create_rotation
|
||||||
from infoscreen.views import delete_rotation
|
from infoscreen.views import delete_rotation
|
||||||
|
from infoscreen.views import CurrentHSLView
|
||||||
from infoscreen.views import createApyItem
|
from infoscreen.views import createApyItem
|
||||||
|
from infoscreen.views import hsl_timetable_settings
|
||||||
from infoscreen.views import get_apy_json
|
from infoscreen.views import get_apy_json
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r"^$", default),
|
url(r'^$', default),
|
||||||
url(r"^admin$", admin),
|
url(r'^admin$', admin),
|
||||||
url(r"^(?P<idx>\d+)$", index),
|
url(r'^(?P<idx>\d+)$', index),
|
||||||
url(r"^items$", info_items),
|
url(r'^items$', info_items),
|
||||||
url(r"^rotation/(?P<idx>\d+)$", rotation),
|
url(r'^rotation/(?P<idx>\d+)$', rotation),
|
||||||
url(r"^rotations$", rotations),
|
url(r'^rotations$', rotations),
|
||||||
url(r"^instance$", createInstance),
|
url(r'^instance$', createInstance),
|
||||||
url(r"^instance/(?P<idx>\d+)$", deleteInstance),
|
url(r'^instance/(?P<idx>\d+)$', deleteInstance),
|
||||||
url(r"^types$", info_types),
|
url(r'^types$', info_types),
|
||||||
url(r"^delete_item/(?P<type_id>\d+)/(?P<idx>\d+)$", delete_info_item),
|
url(r'^delete_item/(?P<type_id>\d+)/(?P<idx>\d+)$', delete_info_item),
|
||||||
url(r"^create_external_image$", createExternalImageInfoItem),
|
url(r'^create_external_image$', createExternalImageInfoItem),
|
||||||
url(r"^create_image$", create_image_item),
|
url(r'^create_image$', create_image_item),
|
||||||
url(r"^create_video$", create_video_item),
|
url(r'^create_video$', create_video_item),
|
||||||
url(r"^create_abbitem$", createABBItem),
|
url(r'^create_abbitem$', createABBItem),
|
||||||
url(r"^create_sossoitem$", createSossoItem),
|
url(r'^create_sossoitem$', createSossoItem),
|
||||||
url(r"^create_lunchitem$", createLunchItem),
|
url(r'^create_eventitem$', createEventItem),
|
||||||
url(r"^create_eventitem$", createEventItem),
|
url(r'^create_hslitem$', createHslItem),
|
||||||
url(r"^create_apyitem$", createApyItem),
|
url(r'^create_apyitem$', createApyItem),
|
||||||
url(r"^create_websiteitem$", createExternalWebsiteItem),
|
url(r'^create_websiteitem$', createExternalWebsiteItem),
|
||||||
url(r"^create_rotation$", create_rotation),
|
url(r'^create_rotation$', create_rotation),
|
||||||
url(r"^delete_rotation/(?P<id>\d+)$", delete_rotation),
|
url(r'^delete_rotation/(?P<id>\d+)$', delete_rotation),
|
||||||
url(r"^apyjson", get_apy_json),
|
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()
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ from django.views.decorators.csrf import ensure_csrf_cookie
|
|||||||
from django.views.decorators.http import require_http_methods
|
from django.views.decorators.http import require_http_methods
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.auth.decorators import permission_required, login_required
|
from django.contrib.auth.decorators import permission_required, login_required
|
||||||
from django.db import DatabaseError
|
|
||||||
from infoscreen.models import UploadFileForm
|
from infoscreen.models import UploadFileForm
|
||||||
|
|
||||||
import sikweb.settings as settings
|
import sikweb.settings as settings
|
||||||
@@ -15,28 +14,21 @@ import logging
|
|||||||
import threading
|
import threading
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from infoscreen.models import (
|
from infoscreen.models import Rotation, InfoItem, InfoInstance
|
||||||
Rotation,
|
from infoscreen.models import (ABBInfoItem, ExternalImageInfoItem,
|
||||||
InfoItem,
|
ImageInfoItem, SossoInfoItem, HslInfoItem)
|
||||||
InfoInstance,
|
from infoscreen.models import EventInfoItem
|
||||||
ABBInfoItem,
|
from infoscreen.models import ExternalWebsiteInfoItem
|
||||||
ExternalImageInfoItem,
|
from infoscreen.models import ImageUploadForm
|
||||||
ImageInfoItem,
|
from infoscreen.models import ApyInfoItem
|
||||||
SossoInfoItem,
|
from infoscreen.models import VideoInfoItem
|
||||||
LunchItem,
|
|
||||||
EventInfoItem,
|
|
||||||
ExternalWebsiteInfoItem,
|
|
||||||
ImageUploadForm,
|
|
||||||
ApyInfoItem,
|
|
||||||
VideoInfoItem,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(login_url="/admin/login")
|
@login_required(login_url='/login')
|
||||||
@permission_required("infoscreen.change_infoinstance", raise_exception=True)
|
@permission_required('infoscreen.change_infoinstance', raise_exception=True)
|
||||||
def admin(request, *args, **kwargs):
|
def admin(request, *args, **kwargs):
|
||||||
"""Render infoscreen admin page."""
|
"""Render infoscreen admin page."""
|
||||||
return render(request, "infoscreen/infoscreen_admin.html", {})
|
return render(request, 'infoscreen_admin.html', {})
|
||||||
|
|
||||||
|
|
||||||
def create_item_generator(model):
|
def create_item_generator(model):
|
||||||
@@ -44,23 +36,20 @@ def create_item_generator(model):
|
|||||||
|
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
@require_http_methods(["POST"])
|
@require_http_methods(["POST"])
|
||||||
@login_required(login_url="/admin/login")
|
@login_required(login_url='/login')
|
||||||
@permission_required("infoscreen.add_infoinstance", raise_exception=True)
|
@permission_required('infoscreen.add_infoinstance', raise_exception=True)
|
||||||
def create_item(request, *args, **kwargs):
|
def create_item(request, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
data = json.loads(request.body.decode("utf-8"))
|
data = json.loads(request.body.decode("utf-8"))
|
||||||
except json.JSONDecodeError:
|
except ValueError:
|
||||||
return HttpResponseBadRequest(
|
return HttpResponseBadRequest(
|
||||||
'{"status":"failure","error":"invalid json supplied"}'
|
'{"status":"failure","error":"invalid json supplied"}')
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
model.create_from_dict(data)
|
model.create_from_dict(data)
|
||||||
return HttpResponse('{"status":"success"}')
|
return HttpResponse('{"status":"success"}')
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
return HttpResponseBadRequest(
|
return HttpResponseBadRequest(
|
||||||
json.dumps({"status": "failure", "error": str(e)})
|
json.dumps({"status": "failure", "error": str(e)}))
|
||||||
)
|
|
||||||
|
|
||||||
return create_item
|
return create_item
|
||||||
|
|
||||||
|
|
||||||
@@ -69,8 +58,8 @@ def delete_item_generator(model):
|
|||||||
|
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
@require_http_methods(["DELETE"])
|
@require_http_methods(["DELETE"])
|
||||||
@login_required(login_url="/admin/login")
|
@login_required(login_url='/login')
|
||||||
@permission_required("infoscreen.delete_infoinstance", raise_exception=True)
|
@permission_required('infoscreen.delete_infoinstance', raise_exception=True)
|
||||||
def delete_item(request, *args, **kwargs):
|
def delete_item(request, *args, **kwargs):
|
||||||
idx = kwargs.pop("idx", 0)
|
idx = kwargs.pop("idx", 0)
|
||||||
try:
|
try:
|
||||||
@@ -82,18 +71,17 @@ def delete_item_generator(model):
|
|||||||
try:
|
try:
|
||||||
item.delete()
|
item.delete()
|
||||||
return HttpResponse('{"status":"success"}')
|
return HttpResponse('{"status":"success"}')
|
||||||
except DatabaseError:
|
except:
|
||||||
resp = HttpResponse('{"error" : "could not delete item"}')
|
resp = HttpResponse('{"error" : "could not delete item"}')
|
||||||
resp.status_code = 500
|
resp.status_code = 500
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
return delete_item
|
return delete_item
|
||||||
|
|
||||||
|
|
||||||
# due to model structure this is little complicated
|
# due to model structure this is little complicated
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
@login_required(login_url="/admin/login")
|
@login_required(login_url='/login')
|
||||||
@permission_required("infoscreen.delete_infoinstance", raise_exception=True)
|
@permission_required('infoscreen.delete_infoinstance', raise_exception=True)
|
||||||
@require_http_methods(["DELETE"])
|
@require_http_methods(["DELETE"])
|
||||||
def delete_info_item(request, *args, **kwargs):
|
def delete_info_item(request, *args, **kwargs):
|
||||||
"""Delete info item."""
|
"""Delete info item."""
|
||||||
@@ -109,7 +97,7 @@ def delete_info_item(request, *args, **kwargs):
|
|||||||
try:
|
try:
|
||||||
item.delete()
|
item.delete()
|
||||||
return HttpResponse('{"status":"success"}')
|
return HttpResponse('{"status":"success"}')
|
||||||
except DatabaseError:
|
except:
|
||||||
resp = HttpResponse('{"error" : "could not delete item"}')
|
resp = HttpResponse('{"error" : "could not delete item"}')
|
||||||
resp.status_code = 500
|
resp.status_code = 500
|
||||||
return resp
|
return resp
|
||||||
@@ -117,65 +105,64 @@ def delete_info_item(request, *args, **kwargs):
|
|||||||
|
|
||||||
@require_http_methods(["POST"])
|
@require_http_methods(["POST"])
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
@login_required(login_url="/admin/login")
|
@login_required(login_url='/login')
|
||||||
@permission_required("infoscreen.add_infoinstance", raise_exception=True)
|
@permission_required('infoscreen.add_infoinstance', raise_exception=True)
|
||||||
def create_image_item(request, *args, **kwargs):
|
def create_image_item(request, *args, **kwargs):
|
||||||
"""Create image Infoscreen item."""
|
"""Create image Infoscreen item."""
|
||||||
form = ImageUploadForm(request.POST, request.FILES)
|
form = ImageUploadForm(request.POST, request.FILES)
|
||||||
if not form.is_valid():
|
if not form.is_valid():
|
||||||
return HttpResponseBadRequest(
|
return HttpResponseBadRequest('{"status": "failure",'
|
||||||
'{"status": "failure",' '"error": "invalid data supplied"}'
|
'"error": "invalid data supplied"}')
|
||||||
)
|
|
||||||
|
|
||||||
img = form.cleaned_data["image"]
|
img = form.cleaned_data['image']
|
||||||
name = form.cleaned_data["name"]
|
name = form.cleaned_data['name']
|
||||||
ImageInfoItem.objects.create(img=img, name=name)
|
ImageInfoItem.objects.create(img=img, name=name)
|
||||||
return HttpResponse('{"status":"success"}')
|
return HttpResponse('{"status":"success"}')
|
||||||
|
|
||||||
|
|
||||||
@require_http_methods(["POST"])
|
@require_http_methods(["POST"])
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
@login_required(login_url="/admin/login")
|
@login_required(login_url='/login')
|
||||||
@permission_required("infoscreen.add_infoinstance", raise_exception=True)
|
@permission_required('infoscreen.add_infoinstance', raise_exception=True)
|
||||||
def create_video_item(request, *args, **kwargs):
|
def create_video_item(request, *args, **kwargs):
|
||||||
"""Create video Infoscreen item."""
|
"""Create video Infoscreen item."""
|
||||||
form = UploadFileForm(request.POST, request.FILES)
|
form = UploadFileForm(request.POST, request.FILES)
|
||||||
if not form.is_valid():
|
if not form.is_valid():
|
||||||
return HttpResponseBadRequest(
|
return HttpResponseBadRequest('{"status": "failure",'
|
||||||
'{"status": "failure",' '"error": "invalid data supplied"}'
|
'"error": "invalid data supplied"}')
|
||||||
)
|
|
||||||
|
|
||||||
video = form.cleaned_data["video"]
|
video = form.cleaned_data['video']
|
||||||
name = form.cleaned_data["name"]
|
name = form.cleaned_data['name']
|
||||||
VideoInfoItem.objects.create(video=video, name=name)
|
VideoInfoItem.objects.create(video=video, name=name)
|
||||||
return HttpResponse('{"status": "success"}')
|
return HttpResponse('{"status": "success"}')
|
||||||
|
|
||||||
|
|
||||||
@require_http_methods(["POST"])
|
@require_http_methods(["POST"])
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
@login_required(login_url="/admin/login")
|
@login_required(login_url='/login')
|
||||||
@permission_required("infoscreen.add_rotation", raise_exception=True)
|
@permission_required('infoscreen.add_rotation', raise_exception=True)
|
||||||
def create_rotation(request, *args, **kwargs):
|
def create_rotation(request, *args, **kwargs):
|
||||||
"""Create rotation."""
|
"""Create rotation."""
|
||||||
try:
|
try:
|
||||||
data = json.loads(request.body.decode("utf-8"))
|
data = json.loads(request.body.decode("utf-8"))
|
||||||
except json.JSONDecodeError:
|
except:
|
||||||
return HttpResponse('{"error": "bad post body!"}', status=400)
|
return HttpResponse('{"error": "bad post body!"}', status=400)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
name = data["name"]
|
name = data["name"]
|
||||||
Rotation.objects.create(name=name)
|
Rotation.objects.create(name=name)
|
||||||
resp = HttpResponse(status=200)
|
resp = HttpResponse(status=200)
|
||||||
except DatabaseError:
|
except:
|
||||||
resp = HttpResponse('{"error" : "could not create rotation!"}', status=400)
|
resp = HttpResponse(
|
||||||
|
'{"error" : "could not create rotation!"}', status=400)
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
|
||||||
@require_http_methods(["DELETE"])
|
@require_http_methods(["DELETE"])
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
@login_required(login_url="/admin/login")
|
@login_required(login_url='/login')
|
||||||
@permission_required("infoscreen.delete_rotation", raise_exception=True)
|
@permission_required('infoscreen.delete_rotation', raise_exception=True)
|
||||||
def delete_rotation(request, *args, **kwargs):
|
def delete_rotation(request, *args, **kwargs):
|
||||||
"""Delete rotation."""
|
"""Delete rotation."""
|
||||||
id = kwargs.pop("id", 0)
|
id = kwargs.pop("id", 0)
|
||||||
@@ -184,8 +171,9 @@ def delete_rotation(request, *args, **kwargs):
|
|||||||
try:
|
try:
|
||||||
Rotation.objects.filter(id=id).delete()
|
Rotation.objects.filter(id=id).delete()
|
||||||
resp = HttpResponse(status=200)
|
resp = HttpResponse(status=200)
|
||||||
except DatabaseError:
|
except:
|
||||||
resp = HttpResponse('{"error" : "could not delete rotation!"}', status=400)
|
resp = HttpResponse(
|
||||||
|
'{"error" : "could not delete rotation!"}', status=400)
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
@@ -194,7 +182,7 @@ createInstance = create_item_generator(InfoInstance)
|
|||||||
deleteInstance = delete_item_generator(InfoInstance)
|
deleteInstance = delete_item_generator(InfoInstance)
|
||||||
createABBItem = create_item_generator(ABBInfoItem)
|
createABBItem = create_item_generator(ABBInfoItem)
|
||||||
createSossoItem = create_item_generator(SossoInfoItem)
|
createSossoItem = create_item_generator(SossoInfoItem)
|
||||||
createLunchItem = create_item_generator(LunchItem)
|
createHslItem = create_item_generator(HslInfoItem)
|
||||||
createExternalImageInfoItem = create_item_generator(ExternalImageInfoItem)
|
createExternalImageInfoItem = create_item_generator(ExternalImageInfoItem)
|
||||||
createExternalWebsiteItem = create_item_generator(ExternalWebsiteInfoItem)
|
createExternalWebsiteItem = create_item_generator(ExternalWebsiteInfoItem)
|
||||||
createEventItem = create_item_generator(EventInfoItem)
|
createEventItem = create_item_generator(EventInfoItem)
|
||||||
|
|||||||
@@ -2,20 +2,19 @@ from django.shortcuts import render
|
|||||||
from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest
|
from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest
|
||||||
from django.views.decorators.http import require_http_methods
|
from django.views.decorators.http import require_http_methods
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import DatabaseError
|
|
||||||
|
|
||||||
from infoscreen.models import Rotation, InfoItem, InfoInstance
|
from infoscreen.models import Rotation, InfoItem, InfoInstance
|
||||||
|
from infoscreen.hsl_fetcher import fetch as hsl_fetch
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
import requests
|
|
||||||
|
|
||||||
|
|
||||||
@require_http_methods(["GET"])
|
@require_http_methods(["GET"])
|
||||||
def index(request, idx, *args, **kwargs):
|
def index(request, idx, *args, **kwargs):
|
||||||
"""Render infoscreen index page."""
|
"""Render infoscreen index page."""
|
||||||
return render(request, "infoscreen/infoscreen_index.html", {"rotation": idx})
|
return render(request, 'infoscreen_index.html', {'rotation': idx})
|
||||||
|
|
||||||
|
|
||||||
@require_http_methods(["GET"])
|
@require_http_methods(["GET"])
|
||||||
@@ -23,7 +22,7 @@ def default(request, *args, **kwargs):
|
|||||||
"""Try getting first rotation item."""
|
"""Try getting first rotation item."""
|
||||||
try:
|
try:
|
||||||
first = Rotation.objects.all()[0].id
|
first = Rotation.objects.all()[0].id
|
||||||
except DatabaseError:
|
except:
|
||||||
first = 0
|
first = 0
|
||||||
return index(request, first, *args, **kwargs)
|
return index(request, first, *args, **kwargs)
|
||||||
|
|
||||||
@@ -32,8 +31,7 @@ def default(request, *args, **kwargs):
|
|||||||
def get_apy_json(request):
|
def get_apy_json(request):
|
||||||
"""Render APY diilikone page."""
|
"""Render APY diilikone page."""
|
||||||
return HttpResponse(
|
return HttpResponse(
|
||||||
requests.get("https://api-diilikone.apy.fi/deals/top-groups").text
|
requests.get("https://api-diilikone.apy.fi/deals/top-groups").text)
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@require_http_methods(["GET"])
|
@require_http_methods(["GET"])
|
||||||
@@ -62,12 +60,10 @@ def info_types(request, *args, **kwargs):
|
|||||||
types = []
|
types = []
|
||||||
classes = InfoItem.get_subclasses()
|
classes = InfoItem.get_subclasses()
|
||||||
for c in classes:
|
for c in classes:
|
||||||
types.append(
|
types.append({
|
||||||
{
|
"name": c.display_name,
|
||||||
"name": c.display_name,
|
"create_template_url": c.get_create_template_url(),
|
||||||
"create_template_url": c.get_create_template_url(),
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
return HttpResponse(json.dumps(types))
|
return HttpResponse(json.dumps(types))
|
||||||
|
|
||||||
|
|
||||||
@@ -81,3 +77,25 @@ def info_items(request, *args, **kwargs):
|
|||||||
items.append(i.get_dict())
|
items.append(i.get_dict())
|
||||||
|
|
||||||
return JsonResponse(items, safe=False)
|
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)
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
from django.contrib import admin
|
|
||||||
from modeltranslation.admin import TranslationAdmin
|
|
||||||
|
|
||||||
from kaehmy.models import Application, Comment, CustomRole, PresetRole
|
|
||||||
|
|
||||||
admin.site.register(Application)
|
|
||||||
admin.site.register(Comment)
|
|
||||||
admin.site.register(CustomRole)
|
|
||||||
admin.site.register(PresetRole, TranslationAdmin)
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class KaehmyConfig(AppConfig):
|
|
||||||
name = "kaehmy"
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
from django import forms
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
|
|
||||||
from kaehmy.models import PresetRole, CustomRole, Application, Comment, BaseRole
|
|
||||||
|
|
||||||
|
|
||||||
class CheckboxSelectMultiple(forms.widgets.CheckboxSelectMultiple):
|
|
||||||
option_template_name = "kaehmy/checkbox_option.html"
|
|
||||||
|
|
||||||
def create_option(
|
|
||||||
self, name, value, label, selected, index, subindex=None, attrs=None
|
|
||||||
):
|
|
||||||
dic = super(CheckboxSelectMultiple, self).create_option(
|
|
||||||
name, value, label, selected, index, subindex, attrs
|
|
||||||
)
|
|
||||||
description = PresetRole.objects.get(id=value).description
|
|
||||||
dic["description"] = description
|
|
||||||
return dic
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(CheckboxSelectMultiple, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationForm(forms.ModelForm):
|
|
||||||
"""Class representing Kaehmy form."""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
"""Meta for class Application."""
|
|
||||||
|
|
||||||
model = Application
|
|
||||||
fields = [
|
|
||||||
"name",
|
|
||||||
"email",
|
|
||||||
"phone_number",
|
|
||||||
"year",
|
|
||||||
"preset_roles",
|
|
||||||
"custom_roles",
|
|
||||||
"custom_role_name",
|
|
||||||
"custom_role_is_board",
|
|
||||||
"text",
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(ApplicationForm, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
self.fields["email"].label = _("Email (not public)")
|
|
||||||
self.fields["phone_number"].label = _("Phone number (not public)")
|
|
||||||
|
|
||||||
custom_roles_exist = CustomRole.objects.all().exists()
|
|
||||||
self.fields["custom_roles"].widget = (
|
|
||||||
forms.widgets.CheckboxSelectMultiple()
|
|
||||||
if custom_roles_exist
|
|
||||||
else forms.HiddenInput()
|
|
||||||
)
|
|
||||||
self.fields["custom_roles"].help_text = ""
|
|
||||||
self.fields["custom_roles"].label = _("Custom roles")
|
|
||||||
self.fields["custom_roles"].queryset = CustomRole.objects.all()
|
|
||||||
|
|
||||||
for cat_id, category in BaseRole.CATEGORIES:
|
|
||||||
key = "preset_roles_{}".format(cat_id)
|
|
||||||
qset = PresetRole.objects.filter(category=cat_id).order_by(
|
|
||||||
"category", "-is_board"
|
|
||||||
)
|
|
||||||
self.fields[key] = forms.ModelMultipleChoiceField(qset)
|
|
||||||
self.fields[key].widget = CheckboxSelectMultiple(
|
|
||||||
attrs={
|
|
||||||
"title": _("Preset roles"),
|
|
||||||
"name": "preset_roles",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
self.fields[key].help_text = ""
|
|
||||||
self.fields[key].queryset = qset
|
|
||||||
self.fields[key].label = _(category)
|
|
||||||
self.fields[key].required = False
|
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
cleaned_data = super(ApplicationForm, self).clean()
|
|
||||||
for key in cleaned_data.keys():
|
|
||||||
if "preset_roles_" in key:
|
|
||||||
cleaned_data["preset_roles"] = (
|
|
||||||
cleaned_data["preset_roles"] | cleaned_data[key]
|
|
||||||
)
|
|
||||||
|
|
||||||
return cleaned_data
|
|
||||||
|
|
||||||
def clean_phone_number(self):
|
|
||||||
"""Clean phone number field."""
|
|
||||||
number = self.cleaned_data.get("phone_number")
|
|
||||||
if number.isdigit():
|
|
||||||
return number
|
|
||||||
else:
|
|
||||||
raise ValidationError(_("Invalid phone number"))
|
|
||||||
|
|
||||||
def clean_custom_role_name(self):
|
|
||||||
"""Check that no other custom role with same name exists."""
|
|
||||||
custom_name = self.cleaned_data.get("custom_role_name")
|
|
||||||
if not CustomRole.objects.filter(name=custom_name).exists():
|
|
||||||
return custom_name
|
|
||||||
else:
|
|
||||||
raise ValidationError(_("Custom role with the same name already exists."))
|
|
||||||
|
|
||||||
def non_role_fields(self):
|
|
||||||
return [
|
|
||||||
self.fields[k]
|
|
||||||
for k in self.fields.keys()
|
|
||||||
if k not in ["preset_roles", "custom_roles"]
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class CommentForm(forms.ModelForm):
|
|
||||||
class Meta:
|
|
||||||
model = Comment
|
|
||||||
fields = ["name", "email", "message", "parent"]
|
|
||||||
@@ -1,199 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.11 on 2018-01-25 22:31
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
import django.utils.timezone
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("webapp", "0037_auto_20180125_2131"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="CommentParent",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.AutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"name",
|
|
||||||
models.CharField(default="", max_length=255, verbose_name="Name"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"email",
|
|
||||||
models.EmailField(default="", max_length=254, verbose_name="Email"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"timestamp",
|
|
||||||
models.DateTimeField(
|
|
||||||
default=django.utils.timezone.now, verbose_name="Timestamp"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="CustomRole",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"baserole_ptr",
|
|
||||||
models.OneToOneField(
|
|
||||||
auto_created=True,
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
parent_link=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
to="webapp.BaseRole",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"verbose_name_plural": "Custom kaehmy roles",
|
|
||||||
"verbose_name": "Custom kaehmy role",
|
|
||||||
},
|
|
||||||
bases=("webapp.baserole",),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="PresetRole",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"presetrole_ptr",
|
|
||||||
models.OneToOneField(
|
|
||||||
auto_created=True,
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
parent_link=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
to="webapp.PresetRole",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"verbose_name_plural": "Preset kaehmy roles",
|
|
||||||
"verbose_name": "Preset kaehmy role",
|
|
||||||
},
|
|
||||||
bases=("webapp.presetrole",),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="TelegramChannel",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.AutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("name", models.CharField(max_length=255)),
|
|
||||||
("channel_id", models.CharField(max_length=255, unique=True)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"verbose_name_plural": "Telegram channels",
|
|
||||||
"verbose_name": "Telegram channel",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="Application",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"commentparent_ptr",
|
|
||||||
models.OneToOneField(
|
|
||||||
auto_created=True,
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
parent_link=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
to="kaehmy.CommentParent",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"phone_number",
|
|
||||||
models.CharField(
|
|
||||||
default="", max_length=10, verbose_name="Phone number"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"year",
|
|
||||||
models.IntegerField(
|
|
||||||
choices=[(1, "1"), (2, "2"), (3, "3"), (4, "4"), (5, "N")],
|
|
||||||
verbose_name="Year",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"text",
|
|
||||||
models.TextField(default="", max_length=300, verbose_name="Text"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"custom_role_name",
|
|
||||||
models.CharField(
|
|
||||||
blank=True, max_length=255, verbose_name="Custom role name"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"custom_role_is_board",
|
|
||||||
models.BooleanField(verbose_name="Board member"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"custom_roles",
|
|
||||||
models.ManyToManyField(
|
|
||||||
blank=True, related_name="forms", to="kaehmy.CustomRole"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"preset_roles",
|
|
||||||
models.ManyToManyField(
|
|
||||||
blank=True, related_name="forms", to="kaehmy.PresetRole"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"verbose_name_plural": "Kaehmylomakkeet",
|
|
||||||
"verbose_name": "Kaehmylomake",
|
|
||||||
},
|
|
||||||
bases=("kaehmy.commentparent",),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="Comment",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"commentparent_ptr",
|
|
||||||
models.OneToOneField(
|
|
||||||
auto_created=True,
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
parent_link=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
to="kaehmy.CommentParent",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("message", models.TextField(verbose_name="Message")),
|
|
||||||
(
|
|
||||||
"parent",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
related_name="messages",
|
|
||||||
to="kaehmy.CommentParent",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"verbose_name_plural": "Kaehmykommentit",
|
|
||||||
"verbose_name": "Kaehmykommentti",
|
|
||||||
},
|
|
||||||
bases=("kaehmy.commentparent",),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
# Generated by Django 2.0.7 on 2018-09-02 16:29
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("webapp", "0047_auto_20180710_2110"),
|
|
||||||
("kaehmy", "0001_initial"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="KaehmyBaseRole",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"baserole_ptr",
|
|
||||||
models.OneToOneField(
|
|
||||||
auto_created=True,
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
parent_link=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
to="webapp.BaseRole",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"category",
|
|
||||||
models.CharField(
|
|
||||||
choices=[
|
|
||||||
("corporate", "Corporate affairs"),
|
|
||||||
("freshman", "Freshmen"),
|
|
||||||
("international", "International"),
|
|
||||||
("external", "External affairs"),
|
|
||||||
("media", "Media"),
|
|
||||||
("tech", "Technology"),
|
|
||||||
("wellbeing", "Wellbeing"),
|
|
||||||
("elepaja", "Elepaja"),
|
|
||||||
("ceremonies", "Ceremonies"),
|
|
||||||
("culture", "Culture"),
|
|
||||||
("studies", "Studies"),
|
|
||||||
("sosso", "Sössö magazine"),
|
|
||||||
("alumni", "Alumni relations"),
|
|
||||||
("others", "Others"),
|
|
||||||
],
|
|
||||||
default="others",
|
|
||||||
max_length=255,
|
|
||||||
verbose_name="Category",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
bases=("webapp.baserole",),
|
|
||||||
),
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name="Application",
|
|
||||||
),
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name="customrole",
|
|
||||||
),
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name="presetrole",
|
|
||||||
),
|
|
||||||
]
|
|
||||||