Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 06723e3f69 | |||
| ba9b5d02b2 | |||
| 242d143a5e |
@@ -3,5 +3,3 @@ show_missing = True
|
||||
[run]
|
||||
omit =
|
||||
*/migrations/*
|
||||
*/admin.py
|
||||
*/translation.py
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "Deploying to development."
|
||||
set -e
|
||||
set -x
|
||||
|
||||
pushd deployment
|
||||
|
||||
docker-compose down
|
||||
docker pull "$1"
|
||||
docker-compose up -d
|
||||
|
||||
popd
|
||||
|
||||
set +x
|
||||
set +e
|
||||
@@ -1,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,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=localhost
|
||||
DB_PORT=5432
|
||||
EMAIL_API_KEY=
|
||||
GROUP_KEY=
|
||||
GOOGLE_CREDS='{}'
|
||||
-13
@@ -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='{}'
|
||||
@@ -0,0 +1,6 @@
|
||||
members/static/js/lib
|
||||
infoscreen/static/js/lib
|
||||
webapp/static/js/lib
|
||||
static/js/lib
|
||||
collected_static
|
||||
venv
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"jquery": true
|
||||
},
|
||||
"globals": {
|
||||
"angular": 1,
|
||||
"noty": 1,
|
||||
"app": 1,
|
||||
"_": 1,
|
||||
"moment": 1
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"rules": {
|
||||
"no-unused-vars": "warn",
|
||||
"accessor-pairs": "error",
|
||||
"array-bracket-spacing": "off",
|
||||
"array-callback-return": "error",
|
||||
"arrow-body-style": "error",
|
||||
"arrow-parens": "error",
|
||||
"arrow-spacing": "error",
|
||||
"block-scoped-var": "off",
|
||||
"block-spacing": "off",
|
||||
"brace-style": "off",
|
||||
"callback-return": "off",
|
||||
"camelcase": "off",
|
||||
"capitalized-comments": "off",
|
||||
"class-methods-use-this": "error",
|
||||
"comma-dangle": "off",
|
||||
"comma-spacing": "off",
|
||||
"comma-style": "off",
|
||||
"complexity": "off",
|
||||
"computed-property-spacing": "off",
|
||||
"consistent-return": "off",
|
||||
"consistent-this": "off",
|
||||
"curly": "off",
|
||||
"default-case": "off",
|
||||
"dot-location": [
|
||||
"error",
|
||||
"property"
|
||||
],
|
||||
"dot-notation": "off",
|
||||
"eol-last": "off",
|
||||
"eqeqeq": "off",
|
||||
"func-call-spacing": "error",
|
||||
"func-name-matching": "error",
|
||||
"func-names": "off",
|
||||
"func-style": "off",
|
||||
"generator-star-spacing": "error",
|
||||
"global-require": "off",
|
||||
"guard-for-in": "off",
|
||||
"handle-callback-err": "off",
|
||||
"id-blacklist": "error",
|
||||
"id-length": "off",
|
||||
"id-match": "error",
|
||||
"indent": "off",
|
||||
"init-declarations": "off",
|
||||
"jsx-quotes": "error",
|
||||
"key-spacing": "off",
|
||||
"keyword-spacing": "off",
|
||||
"line-comment-position": "off",
|
||||
"linebreak-style": "off",
|
||||
"lines-around-comment": "off",
|
||||
"lines-around-directive": "off",
|
||||
"max-depth": "off",
|
||||
"max-len": "off",
|
||||
"max-lines": "off",
|
||||
"max-nested-callbacks": "error",
|
||||
"max-params": "off",
|
||||
"max-statements": "off",
|
||||
"max-statements-per-line": "off",
|
||||
"multiline-ternary": "off",
|
||||
"new-parens": "off",
|
||||
"newline-after-var": "off",
|
||||
"newline-before-return": "off",
|
||||
"newline-per-chained-call": "off",
|
||||
"no-alert": "error",
|
||||
"no-array-constructor": "off",
|
||||
"no-await-in-loop": "error",
|
||||
"no-bitwise": "off",
|
||||
"no-caller": "error",
|
||||
"no-catch-shadow": "off",
|
||||
"no-confusing-arrow": "error",
|
||||
"no-constant-condition": [
|
||||
"error",
|
||||
{
|
||||
"checkLoops": false
|
||||
}
|
||||
],
|
||||
"no-continue": "off",
|
||||
"no-div-regex": "error",
|
||||
"no-duplicate-imports": "error",
|
||||
"no-else-return": "off",
|
||||
"no-empty-function": "off",
|
||||
"no-eq-null": "off",
|
||||
"no-eval": "error",
|
||||
"no-extend-native": "error",
|
||||
"no-extra-bind": "error",
|
||||
"no-extra-label": "error",
|
||||
"no-extra-parens": "off",
|
||||
"no-floating-decimal": "off",
|
||||
"no-implicit-coercion": [
|
||||
"error",
|
||||
{
|
||||
"boolean": false,
|
||||
"number": false,
|
||||
"string": false
|
||||
}
|
||||
],
|
||||
"no-implicit-globals": "off",
|
||||
"no-implied-eval": "error",
|
||||
"no-inline-comments": "off",
|
||||
"no-inner-declarations": [
|
||||
"error",
|
||||
"functions"
|
||||
],
|
||||
"no-invalid-this": "off",
|
||||
"no-iterator": "error",
|
||||
"no-label-var": "error",
|
||||
"no-labels": "error",
|
||||
"no-lone-blocks": "error",
|
||||
"no-lonely-if": "off",
|
||||
"no-loop-func": "error",
|
||||
"no-magic-numbers": "off",
|
||||
"no-mixed-operators": "off",
|
||||
"no-mixed-requires": "error",
|
||||
"no-multi-assign": "off",
|
||||
"no-multi-spaces": "off",
|
||||
"no-multi-str": "error",
|
||||
"no-multiple-empty-lines": "off",
|
||||
"no-native-reassign": "off",
|
||||
"no-negated-condition": "off",
|
||||
"no-negated-in-lhs": "error",
|
||||
"no-nested-ternary": "off",
|
||||
"no-new": "error",
|
||||
"no-new-func": "off",
|
||||
"no-new-object": "error",
|
||||
"no-new-require": "error",
|
||||
"no-new-wrappers": "error",
|
||||
"no-octal-escape": "error",
|
||||
"no-param-reassign": "off",
|
||||
"no-path-concat": "error",
|
||||
"no-plusplus": "off",
|
||||
"no-process-env": "error",
|
||||
"no-process-exit": "error",
|
||||
"no-proto": "error",
|
||||
"no-prototype-builtins": "off",
|
||||
"no-restricted-globals": "error",
|
||||
"no-restricted-imports": "error",
|
||||
"no-restricted-modules": "error",
|
||||
"no-restricted-properties": "error",
|
||||
"no-restricted-syntax": "error",
|
||||
"no-return-assign": "off",
|
||||
"no-return-await": "error",
|
||||
"no-script-url": "error",
|
||||
"no-self-compare": "error",
|
||||
"no-sequences": "off",
|
||||
"no-shadow": "off",
|
||||
"no-shadow-restricted-names": "error",
|
||||
"no-spaced-func": "error",
|
||||
"no-sync": "error",
|
||||
"no-tabs": "off",
|
||||
"no-template-curly-in-string": "error",
|
||||
"no-ternary": "off",
|
||||
"no-throw-literal": "off",
|
||||
"no-trailing-spaces": "off",
|
||||
"no-undef-init": "error",
|
||||
"no-undefined": "off",
|
||||
"no-underscore-dangle": "off",
|
||||
"no-unmodified-loop-condition": "error",
|
||||
"no-unneeded-ternary": [
|
||||
"error",
|
||||
{
|
||||
"defaultAssignment": true
|
||||
}
|
||||
],
|
||||
"no-unused-expressions": "off",
|
||||
"no-use-before-define": "off",
|
||||
"no-useless-call": "off",
|
||||
"no-useless-computed-key": "error",
|
||||
"no-useless-concat": "error",
|
||||
"no-useless-constructor": "error",
|
||||
"no-useless-escape": "off",
|
||||
"no-useless-rename": "error",
|
||||
"no-useless-return": "error",
|
||||
"no-var": "off",
|
||||
"no-void": "off",
|
||||
"no-warning-comments": "off",
|
||||
"no-whitespace-before-property": "error",
|
||||
"no-with": "error",
|
||||
"object-curly-newline": "off",
|
||||
"object-curly-spacing": "off",
|
||||
"object-property-newline": "off",
|
||||
"object-shorthand": "off",
|
||||
"one-var": "off",
|
||||
"one-var-declaration-per-line": "off",
|
||||
"operator-assignment": "off",
|
||||
"operator-linebreak": "off",
|
||||
"padded-blocks": "off",
|
||||
"prefer-arrow-callback": "off",
|
||||
"prefer-const": "error",
|
||||
"prefer-destructuring": [
|
||||
"error",
|
||||
{
|
||||
"array": false,
|
||||
"object": false
|
||||
}
|
||||
],
|
||||
"prefer-numeric-literals": "error",
|
||||
"prefer-promise-reject-errors": "error",
|
||||
"prefer-reflect": "off",
|
||||
"prefer-rest-params": "off",
|
||||
"prefer-spread": "off",
|
||||
"prefer-template": "off",
|
||||
"quote-props": "off",
|
||||
"quotes": "off",
|
||||
"radix": "off",
|
||||
"require-await": "error",
|
||||
"require-jsdoc": "off",
|
||||
"rest-spread-spacing": "error",
|
||||
"semi": "off",
|
||||
"semi-spacing": "off",
|
||||
"sort-imports": "error",
|
||||
"sort-keys": "off",
|
||||
"sort-vars": "off",
|
||||
"space-before-blocks": "off",
|
||||
"space-before-function-paren": "off",
|
||||
"space-in-parens": "off",
|
||||
"space-infix-ops": "off",
|
||||
"space-unary-ops": [
|
||||
"error",
|
||||
{
|
||||
"nonwords": false,
|
||||
"words": false
|
||||
}
|
||||
],
|
||||
"spaced-comment": "off",
|
||||
"strict": "off",
|
||||
"symbol-description": "error",
|
||||
"template-curly-spacing": "error",
|
||||
"unicode-bom": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"valid-jsdoc": "off",
|
||||
"vars-on-top": "off",
|
||||
"wrap-iife": "off",
|
||||
"wrap-regex": "off",
|
||||
"yield-star-spacing": "error",
|
||||
"yoda": "off"
|
||||
}
|
||||
}
|
||||
+19
-10
@@ -1,14 +1,23 @@
|
||||
.DS_Store
|
||||
.env
|
||||
*.swp
|
||||
sikweb/settings.py
|
||||
*~
|
||||
*.pyc
|
||||
/collected_static/
|
||||
/media/
|
||||
logs/
|
||||
*.sqlite3
|
||||
uwsgi.ini
|
||||
uwsgi.log
|
||||
members/logs/*
|
||||
node_modules/
|
||||
.coverage
|
||||
.vscode/
|
||||
.idea/
|
||||
logs/
|
||||
/media/
|
||||
node_modules/
|
||||
/.coverage
|
||||
db.sqlite3
|
||||
requirements_henu.txt
|
||||
/collected_static/
|
||||
mydatabase
|
||||
settings.json
|
||||
.vscode/
|
||||
.DS_Store
|
||||
*.code-workspace
|
||||
venv/
|
||||
.venv/
|
||||
sik_test
|
||||
venv/
|
||||
+92
-179
@@ -1,189 +1,102 @@
|
||||
stages:
|
||||
- setup
|
||||
- audit
|
||||
- lint
|
||||
- test
|
||||
- publish
|
||||
- deploy
|
||||
- cleanup
|
||||
|
||||
install:
|
||||
image: node:22
|
||||
stage: setup
|
||||
only:
|
||||
- pushes
|
||||
script:
|
||||
- npm ci
|
||||
artifacts:
|
||||
paths:
|
||||
- node_modules
|
||||
expire_in: 1 week
|
||||
|
||||
audit:
|
||||
image: python:3.12.9
|
||||
stage: audit
|
||||
allow_failure: true
|
||||
only:
|
||||
- pushes
|
||||
needs: []
|
||||
before_script:
|
||||
- pip install poetry==2.0.1
|
||||
- poetry config virtualenvs.create false
|
||||
- poetry install --no-interaction --no-ansi
|
||||
script:
|
||||
- safety check
|
||||
- test
|
||||
- lint
|
||||
- publish
|
||||
- deploy
|
||||
|
||||
test:
|
||||
image: python:3.12.9
|
||||
stage: test
|
||||
only:
|
||||
- pushes
|
||||
needs: []
|
||||
services:
|
||||
- postgres:12
|
||||
variables:
|
||||
POSTGRES_DB: ci
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
|
||||
DB_HOST: postgres
|
||||
before_script:
|
||||
- pip install poetry==2.0.1
|
||||
- poetry config virtualenvs.create false
|
||||
- poetry install --no-interaction --no-ansi
|
||||
script:
|
||||
- python manage.py migrate --noinput
|
||||
- python manage.py createdefaultadmin
|
||||
- python manage.py test
|
||||
image: python:3.5
|
||||
stage: test
|
||||
services:
|
||||
- postgres:latest
|
||||
variables:
|
||||
POSTGRES_DB: ci
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
|
||||
script:
|
||||
- python -V
|
||||
- pip install -r requirements.txt
|
||||
- cp sikweb/settings-sample.py sikweb/default_settings.py
|
||||
- cp sikweb/.ci-settings.py sikweb/settings.py
|
||||
- python manage.py migrate --noinput
|
||||
- python manage.py createdefaultadmin
|
||||
- python manage.py test
|
||||
|
||||
lint:py:
|
||||
image: python:3.12.9
|
||||
stage: lint
|
||||
only:
|
||||
- pushes
|
||||
needs: []
|
||||
script:
|
||||
- pip install black==22.3.0
|
||||
- black --check .
|
||||
pycodestyle:
|
||||
image: python:3.5
|
||||
stage: lint
|
||||
script:
|
||||
- pip install pycodestyle
|
||||
- pycodestyle --config=setup.cfg --count .
|
||||
|
||||
lint:js:
|
||||
image: node:22
|
||||
stage: lint
|
||||
only:
|
||||
- pushes
|
||||
needs: ["install"]
|
||||
script:
|
||||
- npm run lint:js
|
||||
eslint:
|
||||
image: node:alpine
|
||||
stage: lint
|
||||
before_script:
|
||||
- npm install
|
||||
script:
|
||||
- npm run eslint
|
||||
|
||||
lint:md:
|
||||
image: node:22
|
||||
stage: lint
|
||||
only:
|
||||
- pushes
|
||||
needs: ["install"]
|
||||
script:
|
||||
- npm run lint:md
|
||||
remark:
|
||||
image: node:alpine
|
||||
stage: lint
|
||||
before_script:
|
||||
- npm install
|
||||
script:
|
||||
- npm run remark
|
||||
|
||||
publish:
|
||||
image: docker:25-cli
|
||||
stage: publish
|
||||
needs: ["test", "lint:py", "lint:js", "lint:md"]
|
||||
services:
|
||||
- docker:25-dind
|
||||
only:
|
||||
- develop
|
||||
- master
|
||||
script:
|
||||
- docker info
|
||||
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
|
||||
- docker build . -t "$IMAGE_NAME"
|
||||
- docker push "$IMAGE_NAME"
|
||||
stage: publish
|
||||
image: docker:stable
|
||||
services:
|
||||
- docker:stable-dind
|
||||
only:
|
||||
- develop
|
||||
script:
|
||||
- docker info
|
||||
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
|
||||
- docker build . -t "$IMAGE_NAME"
|
||||
- docker push "$IMAGE_NAME"
|
||||
|
||||
deploy:dev:
|
||||
image: docker:25-cli
|
||||
stage: deploy
|
||||
only:
|
||||
- develop
|
||||
environment:
|
||||
name: dev
|
||||
url: http://api.dev.sahkoinsinoorikilta.fi
|
||||
variables:
|
||||
DOCKER_HOST: $DEV_CI_DOCKER_HOST
|
||||
DOCKER_TLS_VERIFY: 1
|
||||
before_script:
|
||||
- mkdir -p ~/.docker
|
||||
- echo "$DEV_TLSCACERT" > ~/.docker/ca.pem
|
||||
- echo "$DEV_TLSCERT" > ~/.docker/cert.pem
|
||||
- echo "$DEV_TLSKEY" > ~/.docker/key.pem
|
||||
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
|
||||
script:
|
||||
- docker stack deploy --with-registry-auth -c stack-compose-dev.yml "$SERVICE_NAME"
|
||||
after_script:
|
||||
- docker logout "$CI_REGISTRY"
|
||||
deploy_dev:
|
||||
stage: deploy
|
||||
image: alpine:latest
|
||||
environment:
|
||||
name: dev
|
||||
url: http://web.sik.party:8000
|
||||
only:
|
||||
- develop
|
||||
before_script:
|
||||
- pwd
|
||||
- apk add --update openssh
|
||||
- ssh -V
|
||||
- mkdir -p ~/.ssh
|
||||
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
|
||||
- chmod 600 ~/.ssh/id_rsa
|
||||
- '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
|
||||
script:
|
||||
- scp docker-compose.yml $DEV_SSH_USER@$DEV_SSH_HOST:~/deployment/docker-compose.yml
|
||||
- scp .deploy_dev.sh $DEV_SSH_USER@$DEV_SSH_HOST:~/deployment/deploy_dev.sh
|
||||
- ssh $DEV_SSH_USER@$DEV_SSH_HOST "docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY"
|
||||
- ssh $DEV_SSH_USER@$DEV_SSH_HOST "bash ~/deployment/deploy_dev.sh \"$IMAGE_NAME\""
|
||||
|
||||
deploy:production:
|
||||
stage: deploy
|
||||
image: docker:25-cli
|
||||
only:
|
||||
- master
|
||||
environment:
|
||||
name: production
|
||||
url: https://api.sahkoinsinoorikilta.fi
|
||||
when: manual
|
||||
variables:
|
||||
DOCKER_HOST: $CI_DOCKER_HOST
|
||||
DOCKER_TLS_VERIFY: 1
|
||||
before_script:
|
||||
- mkdir -p ~/.docker
|
||||
- echo "$TLSCACERT" > ~/.docker/ca.pem
|
||||
- echo "$TLSCERT" > ~/.docker/cert.pem
|
||||
- echo "$TLSKEY" > ~/.docker/key.pem
|
||||
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
|
||||
script:
|
||||
- docker stack deploy --with-registry-auth -c stack-compose.yml "$SERVICE_NAME"
|
||||
after_script:
|
||||
- docker logout "$CI_REGISTRY"
|
||||
|
||||
docker_prune:dev:
|
||||
image: docker:stable
|
||||
stage: cleanup
|
||||
only:
|
||||
- schedules
|
||||
environment:
|
||||
name: dev
|
||||
url: http://api.dev.sahkoinsinoorikilta.fi
|
||||
variables:
|
||||
DOCKER_HOST: $DEV_CI_DOCKER_HOST
|
||||
DOCKER_TLS_VERIFY: 1
|
||||
before_script:
|
||||
- mkdir -p ~/.docker
|
||||
- echo "$DEV_TLSCACERT" > ~/.docker/ca.pem
|
||||
- echo "$DEV_TLSCERT" > ~/.docker/cert.pem
|
||||
- echo "$DEV_TLSKEY" > ~/.docker/key.pem
|
||||
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
|
||||
script:
|
||||
- docker system prune
|
||||
after_script:
|
||||
- docker logout "$CI_REGISTRY"
|
||||
|
||||
docker_prune:prod:
|
||||
image: docker:stable
|
||||
stage: cleanup
|
||||
only:
|
||||
- schedules
|
||||
environment:
|
||||
name: production
|
||||
url: https://api.sahkoinsinoorikilta.fi
|
||||
variables:
|
||||
DOCKER_HOST: $CI_DOCKER_HOST
|
||||
DOCKER_TLS_VERIFY: 1
|
||||
before_script:
|
||||
- mkdir -p ~/.docker
|
||||
- echo "$TLSCACERT" > ~/.docker/ca.pem
|
||||
- echo "$TLSCERT" > ~/.docker/cert.pem
|
||||
- echo "$TLSKEY" > ~/.docker/key.pem
|
||||
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
|
||||
script:
|
||||
- docker system prune
|
||||
after_script:
|
||||
- docker logout "$CI_REGISTRY"
|
||||
deploy_production:
|
||||
stage: deploy
|
||||
image: alpine:latest
|
||||
environment:
|
||||
name: production
|
||||
url: https://sika.sahkoinsinoorikilta.fi
|
||||
when: manual
|
||||
only:
|
||||
- master
|
||||
before_script:
|
||||
- pwd
|
||||
- apk add --update openssh
|
||||
- ssh -V
|
||||
- mkdir -p ~/.ssh
|
||||
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
|
||||
- chmod 600 ~/.ssh/id_rsa
|
||||
- echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
|
||||
script:
|
||||
- ssh $PROD_SSH_USER@$PROD_SSH_HOST "zsh ~/deploy.sh"
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
PURPLE='\033[0;35m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
. "${VIRTUAL_ENV}/bin/activate"
|
||||
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
printf "${PURPLE}Failed to find virtualenv. Skipping pre-commit hook.\n${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
npm run lint
|
||||
+1
-1
@@ -1 +1 @@
|
||||
3.12.9
|
||||
3.6.8
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
global_static
|
||||
+7
-28
@@ -1,29 +1,8 @@
|
||||
FROM python:3.12.9-slim-bullseye AS builder
|
||||
FROM python:3.5
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
COPY . ./
|
||||
|
||||
ENV POETRY_VERSION=2.0.1
|
||||
|
||||
RUN pip install "poetry==$POETRY_VERSION"
|
||||
RUN poetry self add poetry-plugin-export
|
||||
RUN poetry export --without-hashes > requirements.txt
|
||||
|
||||
FROM python:3.12.9-slim-bullseye 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"]
|
||||
ENV IS_DOCKER 1
|
||||
RUN mkdir /code
|
||||
WORKDIR /code
|
||||
ADD requirements.txt /code/
|
||||
RUN env
|
||||
ADD . /code/
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
[[source]]
|
||||
name = "pypi"
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
|
||||
[dev-packages]
|
||||
flake8 = "*"
|
||||
autopep8 = "*"
|
||||
|
||||
[packages]
|
||||
decorator = "==4.0.9"
|
||||
ipython = "==4.2.0"
|
||||
pexpect = "==4.1.0"
|
||||
pickleshare = "==0.7.2"
|
||||
ptyprocess = "==0.5.1"
|
||||
pytz = "==2016.4"
|
||||
simplegeneric = "==0.8.1"
|
||||
traitlets = "==4.2.1"
|
||||
requests = "==2.11.1"
|
||||
django-nocaptcha-recaptcha = "==0.0.19"
|
||||
django-cors-headers = "==2.0.1"
|
||||
djangorestframework = "==3.8.2"
|
||||
djangorestframework-jwt = "==1.11.0"
|
||||
coverage = "==4.3.4"
|
||||
django-nose = "==1.4.5"
|
||||
nose-exclude = "==0.5.0"
|
||||
psycopg2-binary = "==2.7.6.1"
|
||||
django-bootstrap3 = "==8.2.3"
|
||||
django-tables2 = "==1.6.1"
|
||||
pycodestyle = "==2.3.1"
|
||||
dealer = "==2.0.5"
|
||||
django-modeltranslation = "==0.13b1"
|
||||
django-auditlog = "==0.4.5"
|
||||
django-phonenumber-field = "==1.3.0"
|
||||
django-autocomplete-light = "==3.2.10"
|
||||
six = "==1.10.0"
|
||||
django-suit = "==0.2.26"
|
||||
telepot = "==12.3"
|
||||
pyexcel = "==0.5.10"
|
||||
pyexcel-xlsx = "==0.5.5"
|
||||
django-import-export = "==0.7.0"
|
||||
openpyxl = "==2.4.11"
|
||||
django-app-namespace-template-loader = "==0.4.1"
|
||||
django-filter = "==2.0.0"
|
||||
"backports.shutil_get_terminal_size" = "==1.0.0"
|
||||
Django = "==2.1.5"
|
||||
ipython_genutils = "==0.1.0"
|
||||
Pillow = "==5.4.1"
|
||||
PyJWT = "==1.6.4"
|
||||
|
||||
[requires]
|
||||
python_version = "3.6"
|
||||
Generated
+706
@@ -0,0 +1,706 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "fd7aa96e8c4fc9e3fa88b6f3b28a1101d7efd8b58e964fbece854850acf8bcea"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.6"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"aiohttp": {
|
||||
"hashes": [
|
||||
"sha256:00d198585474299c9c3b4f1d5de1a576cc230d562abc5e4a0e81d71a20a6ca55",
|
||||
"sha256:0155af66de8c21b8dba4992aaeeabf55503caefae00067a3b1139f86d0ec50ed",
|
||||
"sha256:09654a9eca62d1bd6d64aa44db2498f60a5c1e0ac4750953fdd79d5c88955e10",
|
||||
"sha256:199f1d106e2b44b6dacdf6f9245493c7d716b01d0b7fbe1959318ba4dc64d1f5",
|
||||
"sha256:296f30dedc9f4b9e7a301e5cc963012264112d78a1d3094cd83ef148fdf33ca1",
|
||||
"sha256:368ed312550bd663ce84dc4b032a962fcb3c7cae099dbbd48663afc305e3b939",
|
||||
"sha256:40d7ea570b88db017c51392349cf99b7aefaaddd19d2c78368aeb0bddde9d390",
|
||||
"sha256:629102a193162e37102c50713e2e31dc9a2fe7ac5e481da83e5bb3c0cee700aa",
|
||||
"sha256:6d5ec9b8948c3d957e75ea14d41e9330e1ac3fed24ec53766c780f82805140dc",
|
||||
"sha256:87331d1d6810214085a50749160196391a712a13336cd02ce1c3ea3d05bcf8d5",
|
||||
"sha256:9a02a04bbe581c8605ac423ba3a74999ec9d8bce7ae37977a3d38680f5780b6d",
|
||||
"sha256:9c4c83f4fa1938377da32bc2d59379025ceeee8e24b89f72fcbccd8ca22dc9bf",
|
||||
"sha256:9cddaff94c0135ee627213ac6ca6d05724bfe6e7a356e5e09ec57bd3249510f6",
|
||||
"sha256:a25237abf327530d9561ef751eef9511ab56fd9431023ca6f4803f1994104d72",
|
||||
"sha256:a5cbd7157b0e383738b8e29d6e556fde8726823dae0e348952a61742b21aeb12",
|
||||
"sha256:a97a516e02b726e089cffcde2eea0d3258450389bbac48cbe89e0f0b6e7b0366",
|
||||
"sha256:acc89b29b5f4e2332d65cd1b7d10c609a75b88ef8925d487a611ca788432dfa4",
|
||||
"sha256:b05bd85cc99b06740aad3629c2585bda7b83bd86e080b44ba47faf905fdf1300",
|
||||
"sha256:c2bec436a2b5dafe5eaeb297c03711074d46b6eb236d002c13c42f25c4a8ce9d",
|
||||
"sha256:cc619d974c8c11fe84527e4b5e1c07238799a8c29ea1c1285149170524ba9303",
|
||||
"sha256:d4392defd4648badaa42b3e101080ae3313e8f4787cb517efd3f5b8157eaefd6",
|
||||
"sha256:e1c3c582ee11af7f63a34a46f0448fca58e59889396ffdae1f482085061a2889"
|
||||
],
|
||||
"version": "==3.5.4"
|
||||
},
|
||||
"async-timeout": {
|
||||
"hashes": [
|
||||
"sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f",
|
||||
"sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"
|
||||
],
|
||||
"version": "==3.0.1"
|
||||
},
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
"sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79",
|
||||
"sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"
|
||||
],
|
||||
"version": "==19.1.0"
|
||||
},
|
||||
"babel": {
|
||||
"hashes": [
|
||||
"sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669",
|
||||
"sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23"
|
||||
],
|
||||
"version": "==2.6.0"
|
||||
},
|
||||
"backports.csv": {
|
||||
"hashes": [
|
||||
"sha256:1277dfff73130b2e106bf3dd347adb3c5f6c4340882289d88f31240da92cbd6d",
|
||||
"sha256:21f6e09bab589e6c1f877edbc40277b65e626262a86e69a70137db714eaac5ce"
|
||||
],
|
||||
"version": "==1.0.7"
|
||||
},
|
||||
"backports.shutil-get-terminal-size": {
|
||||
"hashes": [
|
||||
"sha256:0975ba55054c15e346944b38956a4c9cbee9009391e41b86c68990effb8c1f64",
|
||||
"sha256:713e7a8228ae80341c70586d1cc0a8caa5207346927e23d09dcbcaf18eadec80"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.0.0"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||
],
|
||||
"version": "==3.0.4"
|
||||
},
|
||||
"coverage": {
|
||||
"hashes": [
|
||||
"sha256:01406019418aabb2d4741647cc79b0e0deb0b8c5a6f936936c303e2f82ec8e5b",
|
||||
"sha256:01a07b2b9212d4da3a1294436b58ac53f1d7aa445bda666648a5357048dc7ef3",
|
||||
"sha256:024682371464c6e3caa975aba12b4d5428f35613489340fce1334c74d590a057",
|
||||
"sha256:07c15c4a2287116a41d5966f1f5a7be765640c2e5a1917f882850a24615db6d3",
|
||||
"sha256:1d23dea598fb4d61a8577d0eb0cb2b7932db0c8d2e1394088ad5f64e3fe1febf",
|
||||
"sha256:1eeb9de833c3b976ee118a8d838af437bfa596bf60a5bf0705f4370e6d181a52",
|
||||
"sha256:229ab9c0d53c55d698b8784d53077bef7a5f1fb5d27e90dc7b6f91243b024513",
|
||||
"sha256:2f5a8bf29bdc69976d0913745daab11f8265e46ec41153f5e1e1794088019dad",
|
||||
"sha256:2f959bc1b40a3ef2c5f0c7bc282226d6d4bd585b239bcce321013afc18ff0a0f",
|
||||
"sha256:36407249a0b6669c6ad4425b0f29685579df745480c03afa70f101f09f4eead3",
|
||||
"sha256:3efa49e3da8f32071ee3d5d464cc6b6f8818524d4099b4a94b86a70b8c88d4f5",
|
||||
"sha256:422bcc6270e1c0cd9043048ce244f49072e9bd78a2c028c2ad2cfd58c79f5936",
|
||||
"sha256:4fa2b181c3bf94cfdf841148d5d9abcab1890188dd908a639bcf7a38c50092bc",
|
||||
"sha256:57c0c217270e628380f4befbbf8c5312b88ba7d81fd3d1b2218a25a2608f603c",
|
||||
"sha256:6ae76a6cd594107ad45525278e8addeae4628a59c8cde3999548d7fe1646465b",
|
||||
"sha256:6d3c762c87062a29771015f942752caef42fcc7fe4be2b03186f96788242290c",
|
||||
"sha256:8a82664931a071399d703d65af2521e2202b34f2d8db20fa22a922fec0339022",
|
||||
"sha256:8b282292973a1dc4eccfcc0776e0fde75b5b3de2e35164c2d854f7dd80149e4b",
|
||||
"sha256:9a7874ca91cee8714277cd6d1b52374809ab925bf6ae92607bf02509019caadb",
|
||||
"sha256:9c3e6551597593c1afedcbccf1371995f94457aea82cac726d1f3a25f4507386",
|
||||
"sha256:a791068e1bbe443fcd3179b1c180c27a7fc58c1554b0d10311b7659d2d2d76f5",
|
||||
"sha256:adf04843188418b012dd1974e397a7ac3faa1855cbcd69083e3af4da6de9dd81",
|
||||
"sha256:adfbbd4a1d22fd77b13ff992946b19873407e035504abe9ba537494fe013300f",
|
||||
"sha256:b25aa3531220faaf1727fc29bc000798476b4a30f429dc07898d5da48caefa15",
|
||||
"sha256:c12f34c0b50e9e8bf8c049b6c8ca59929c33cea4b1c48362c99c36838c1ee025",
|
||||
"sha256:c736faa1688222a6c8a5d8be4b66ec373ad6dab27fced8ca0d2c80fed70ac6e3",
|
||||
"sha256:ca36d83cd591d027952e5019149c4386e7058cd674bf8cb52dc622f768d689e9",
|
||||
"sha256:e1fb21a807aa0b5cc79806d8ef09078acaa83f994e15f0f7277489ca8eda51b7",
|
||||
"sha256:e53199ae110cb7e250dd5505fde34452514f4eb2f1fb7532270d2ea037454b09",
|
||||
"sha256:ea9808001dcf34d368cbef430e7885fdc76a2cf8ea96a8ed8b653797dd9555bb",
|
||||
"sha256:eaaefe0f6aa33de5a65f48dd0040d7fe08cac9ac6c35a56d0a7db109c3e733df",
|
||||
"sha256:f27772c9ee88ed3f2a784181f3d1724561499e7e448ed1706153336baa706bd5",
|
||||
"sha256:f99066d76274800145a2e658026b30962eb5079346249197e88b55c9a7855e6a",
|
||||
"sha256:fd3373ccd561b79932d12a986674e642816cfc4db4507b6a22ab30c318a85429"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.3.4"
|
||||
},
|
||||
"dealer": {
|
||||
"hashes": [
|
||||
"sha256:0a5a536fdecd9c7679534a19c59392cd21989037c14782328970a185b39e7560",
|
||||
"sha256:baaac37a4c7928545cb8b0335f48abd0ea51a0274159a9a989afb8b71f8b11c3"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.0.5"
|
||||
},
|
||||
"decorator": {
|
||||
"hashes": [
|
||||
"sha256:90022e83316363788a55352fe39cfbed357aa3a71d90e5f2803a35471de4bba8",
|
||||
"sha256:f4718552326c99544a6ec602d96b7d03ef61180cf4a492c515ecb2438dd14ccc"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.0.9"
|
||||
},
|
||||
"defusedxml": {
|
||||
"hashes": [
|
||||
"sha256:24d7f2f94f7f3cb6061acb215685e5125fbcdc40a857eff9de22518820b0a4f4",
|
||||
"sha256:702a91ade2968a82beb0db1e0766a6a273f33d4616a6ce8cde475d8e09853b20"
|
||||
],
|
||||
"version": "==0.5.0"
|
||||
},
|
||||
"diff-match-patch": {
|
||||
"hashes": [
|
||||
"sha256:a809a996d0f09b9bbd59e9bbd0b71eed8c807922512910e05cbd3f9480712ddb"
|
||||
],
|
||||
"version": "==20181111"
|
||||
},
|
||||
"django": {
|
||||
"hashes": [
|
||||
"sha256:a32c22af23634e1d11425574dce756098e015a165be02e4690179889b207c7a8",
|
||||
"sha256:d6393918da830530a9516bbbcbf7f1214c3d733738779f06b0f649f49cc698c3"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.1.5"
|
||||
},
|
||||
"django-app-namespace-template-loader": {
|
||||
"hashes": [
|
||||
"sha256:356539413b5d1de0eff91aea7a03806b5ef6874ee5420ea8c273f72bbc601d74",
|
||||
"sha256:7a450985479a2e07fe8a1e4e8208fc9e1d8b35503526dd28eba5f8ad4ba31d4e"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.4.1"
|
||||
},
|
||||
"django-auditlog": {
|
||||
"hashes": [
|
||||
"sha256:70bfc673e7023d91ab8449d745425e7a4ce5eaaf2bdcbfb9b1a2209a7af60b03"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.4.5"
|
||||
},
|
||||
"django-autocomplete-light": {
|
||||
"hashes": [
|
||||
"sha256:5ccb1c8c4b75cf72bc5dabd920190ea1ca3a340f56fb6b12d07a62202837fa75"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.2.10"
|
||||
},
|
||||
"django-bootstrap3": {
|
||||
"hashes": [
|
||||
"sha256:6f7946d513d6340bb70f25d8ec047bf3bf37bab9e499d3baca99b1aa0ec92a52"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==8.2.3"
|
||||
},
|
||||
"django-cors-headers": {
|
||||
"hashes": [
|
||||
"sha256:638aaba85f96af62557656ec559672f03d7c61769685acc405eacfaba9d4e93f",
|
||||
"sha256:c766daf9eefcb9536af9817703ea29124fffee06870f9e523b75144b4d39a694"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.0.1"
|
||||
},
|
||||
"django-filter": {
|
||||
"hashes": [
|
||||
"sha256:6f4e4bc1a11151178520567b50320e5c32f8edb552139d93ea3e30613b886f56",
|
||||
"sha256:86c3925020c27d072cdae7b828aaa5d165c2032a629abbe3c3a1be1edae61c58"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.0.0"
|
||||
},
|
||||
"django-import-export": {
|
||||
"hashes": [
|
||||
"sha256:6e748fcc647fe2a82a55136ebcbe806a45fd7fb5b1e32b33759181f1e67eeb1b",
|
||||
"sha256:7e7ebeb40702eafeb2e770914c01b9961063f472b3b395eeffbea5f39efa7257"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.7.0"
|
||||
},
|
||||
"django-jsonfield": {
|
||||
"hashes": [
|
||||
"sha256:25e53eae8bda165721773145ee0f8ae53b746bb3051b32f2821ba84d79aa77ef",
|
||||
"sha256:813c52463fbbb548fe0d85bf935d0bf72e933fae2bb00ce3ba27bf69ff6fd2ad",
|
||||
"sha256:cacf5a21e7c2490109a60f1122c05aa3858a8dc06952ad764831b4428164fc8e"
|
||||
],
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"django-modeltranslation": {
|
||||
"hashes": [
|
||||
"sha256:254ebda6caea5683407e1fb3e45ceaa2275778c6dc2db2b9d4fec3df373c2cdd"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.13b1"
|
||||
},
|
||||
"django-nocaptcha-recaptcha": {
|
||||
"hashes": [
|
||||
"sha256:d2512d5035d5f62aba5009082db28b28995a7e6f4a46713292e0f4f350f337da"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.0.19"
|
||||
},
|
||||
"django-nose": {
|
||||
"hashes": [
|
||||
"sha256:5df2df802c607daeeab8ac1e93abf54508ed6133eb93852310f512000124b4a5",
|
||||
"sha256:87663f18cb25f01d56c84ac1ff8a0e6e6a6246264b2549b751cb239d0642e76a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.4.5"
|
||||
},
|
||||
"django-phonenumber-field": {
|
||||
"hashes": [
|
||||
"sha256:8db9d2dc833678b163adabd593cda7ad1dede81a1c18f67c895701fc44dc44f1"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.3.0"
|
||||
},
|
||||
"django-suit": {
|
||||
"hashes": [
|
||||
"sha256:19ed865a478dfca81cb5f50a70317700dd70da92c465093251d0e14330a2b92b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.2.26"
|
||||
},
|
||||
"django-tables2": {
|
||||
"hashes": [
|
||||
"sha256:d5d3ad99580121f7ec46ea9e2420069bbd6d2f33b4fde73a376c6bf27d551146"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.6.1"
|
||||
},
|
||||
"djangorestframework": {
|
||||
"hashes": [
|
||||
"sha256:b6714c3e4b0f8d524f193c91ecf5f5450092c2145439ac2769711f7eba89a9d9",
|
||||
"sha256:c375e4f95a3a64fccac412e36fb42ba36881e52313ec021ef410b40f67cddca4"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.8.2"
|
||||
},
|
||||
"djangorestframework-jwt": {
|
||||
"hashes": [
|
||||
"sha256:5efe33032f3a4518a300dc51a51c92145ad95fb6f4b272e5aa24701db67936a7",
|
||||
"sha256:ab15dfbbe535eede8e2e53adaf52ef0cf018ee27dbfad10cbc4cbec2ab63d38c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.11.0"
|
||||
},
|
||||
"et-xmlfile": {
|
||||
"hashes": [
|
||||
"sha256:614d9722d572f6246302c4491846d2c393c199cfa4edc9af593437691683335b"
|
||||
],
|
||||
"version": "==1.0.1"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
|
||||
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
|
||||
],
|
||||
"version": "==2.8"
|
||||
},
|
||||
"ipython": {
|
||||
"hashes": [
|
||||
"sha256:0480354f25b2f443e4ef1456b48f28ad1caaa6d316fca5a5eaa9ca7745ae7923",
|
||||
"sha256:98452af6450e28c9c742d567d75eb6e3a7b391ad4ce8abd5679c5f85ce7fad00",
|
||||
"sha256:d852fed59da67c7e45cb2192027da8bfd920a7856d295c247a45105968d24d5a",
|
||||
"sha256:dba42f182b5f6f26630d2202efd30383712d9f7d8d8d9896b37ae2145deca616"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.2.0"
|
||||
},
|
||||
"ipython-genutils": {
|
||||
"hashes": [
|
||||
"sha256:0c43fa84e93ad0e4dbecaffc6656ac1caf1a48359b2bb0a5da3af84164e3f49b",
|
||||
"sha256:3a0624a251a26463c9dfa0ffa635ec51c4265380980d9a50d65611c3c2bd82a6",
|
||||
"sha256:6218e9abd612fb5acfb175ea7c7b026006de4df9691d9a73c9b390cfa1a41c2b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.1.0"
|
||||
},
|
||||
"jdcal": {
|
||||
"hashes": [
|
||||
"sha256:948fb8d079e63b4be7a69dd5f0cd618a0a57e80753de8248fd786a8a20658a07",
|
||||
"sha256:ea0a5067c5f0f50ad4c7bdc80abad3d976604f6fb026b0b3a17a9d84bb9046c9"
|
||||
],
|
||||
"version": "==1.4"
|
||||
},
|
||||
"lml": {
|
||||
"hashes": [
|
||||
"sha256:b1bef669dc077a1075fa64b99229b6341085b3b3a98d29c66df1853cc14e6c1a",
|
||||
"sha256:ea5ba817b4adc9e9f5c21725cd2475f912933b7e2dfdf0792aed80077154f63f"
|
||||
],
|
||||
"version": "==0.0.9"
|
||||
},
|
||||
"multidict": {
|
||||
"hashes": [
|
||||
"sha256:024b8129695a952ebd93373e45b5d341dbb87c17ce49637b34000093f243dd4f",
|
||||
"sha256:041e9442b11409be5e4fc8b6a97e4bcead758ab1e11768d1e69160bdde18acc3",
|
||||
"sha256:045b4dd0e5f6121e6f314d81759abd2c257db4634260abcfe0d3f7083c4908ef",
|
||||
"sha256:047c0a04e382ef8bd74b0de01407e8d8632d7d1b4db6f2561106af812a68741b",
|
||||
"sha256:068167c2d7bbeebd359665ac4fff756be5ffac9cda02375b5c5a7c4777038e73",
|
||||
"sha256:148ff60e0fffa2f5fad2eb25aae7bef23d8f3b8bdaf947a65cdbe84a978092bc",
|
||||
"sha256:1d1c77013a259971a72ddaa83b9f42c80a93ff12df6a4723be99d858fa30bee3",
|
||||
"sha256:1d48bc124a6b7a55006d97917f695effa9725d05abe8ee78fd60d6588b8344cd",
|
||||
"sha256:31dfa2fc323097f8ad7acd41aa38d7c614dd1960ac6681745b6da124093dc351",
|
||||
"sha256:34f82db7f80c49f38b032c5abb605c458bac997a6c3142e0d6c130be6fb2b941",
|
||||
"sha256:3d5dd8e5998fb4ace04789d1d008e2bb532de501218519d70bb672c4c5a2fc5d",
|
||||
"sha256:4a6ae52bd3ee41ee0f3acf4c60ceb3f44e0e3bc52ab7da1c2b2aa6703363a3d1",
|
||||
"sha256:4b02a3b2a2f01d0490dd39321c74273fed0568568ea0e7ea23e02bd1fb10a10b",
|
||||
"sha256:4b843f8e1dd6a3195679d9838eb4670222e8b8d01bc36c9894d6c3538316fa0a",
|
||||
"sha256:5de53a28f40ef3c4fd57aeab6b590c2c663de87a5af76136ced519923d3efbb3",
|
||||
"sha256:61b2b33ede821b94fa99ce0b09c9ece049c7067a33b279f343adfe35108a4ea7",
|
||||
"sha256:6a3a9b0f45fd75dc05d8e93dc21b18fc1670135ec9544d1ad4acbcf6b86781d0",
|
||||
"sha256:76ad8e4c69dadbb31bad17c16baee61c0d1a4a73bed2590b741b2e1a46d3edd0",
|
||||
"sha256:7ba19b777dc00194d1b473180d4ca89a054dd18de27d0ee2e42a103ec9b7d014",
|
||||
"sha256:7c1b7eab7a49aa96f3db1f716f0113a8a2e93c7375dd3d5d21c4941f1405c9c5",
|
||||
"sha256:7fc0eee3046041387cbace9314926aa48b681202f8897f8bff3809967a049036",
|
||||
"sha256:8ccd1c5fff1aa1427100ce188557fc31f1e0a383ad8ec42c559aabd4ff08802d",
|
||||
"sha256:8e08dd76de80539d613654915a2f5196dbccc67448df291e69a88712ea21e24a",
|
||||
"sha256:c18498c50c59263841862ea0501da9f2b3659c00db54abfbf823a80787fde8ce",
|
||||
"sha256:c49db89d602c24928e68c0d510f4fcf8989d77defd01c973d6cbe27e684833b1",
|
||||
"sha256:ce20044d0317649ddbb4e54dab3c1bcc7483c78c27d3f58ab3d0c7e6bc60d26a",
|
||||
"sha256:d1071414dd06ca2eafa90c85a079169bfeb0e5f57fd0b45d44c092546fcd6fd9",
|
||||
"sha256:d3be11ac43ab1a3e979dac80843b42226d5d3cccd3986f2e03152720a4297cd7",
|
||||
"sha256:db603a1c235d110c860d5f39988ebc8218ee028f07a7cbc056ba6424372ca31b"
|
||||
],
|
||||
"version": "==4.5.2"
|
||||
},
|
||||
"nose": {
|
||||
"hashes": [
|
||||
"sha256:9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac",
|
||||
"sha256:dadcddc0aefbf99eea214e0f1232b94f2fa9bd98fa8353711dacb112bfcbbb2a",
|
||||
"sha256:f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98"
|
||||
],
|
||||
"version": "==1.3.7"
|
||||
},
|
||||
"nose-exclude": {
|
||||
"hashes": [
|
||||
"sha256:f78fa8b41eeb815f0486414f710f1eea0949e346cfb11d59ba6295ed69e84304"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.5.0"
|
||||
},
|
||||
"odfpy": {
|
||||
"hashes": [
|
||||
"sha256:596021f0519623ca8717331951c95e3b8d7b21e86edc7efe8cb650a0d0f59a2b"
|
||||
],
|
||||
"version": "==1.4.0"
|
||||
},
|
||||
"openpyxl": {
|
||||
"hashes": [
|
||||
"sha256:626d38647c063d55803ef4971c4d43226538d4e95cb6260c094e363ee33e10c7"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.4.11"
|
||||
},
|
||||
"pexpect": {
|
||||
"hashes": [
|
||||
"sha256:09b0a7727ce012e0fa668ef848591102d6667521655f4e72d51197c872cb9fb9",
|
||||
"sha256:c381c60f1987355b65df8f08a27f428831914c8a81091bd1778ac336fa2f27e7"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.1.0"
|
||||
},
|
||||
"phonenumberslite": {
|
||||
"hashes": [
|
||||
"sha256:2cb034b158314ca3dc034b417a64777c14b74f27f47b451270686e22eefd57d3",
|
||||
"sha256:fb9212bb8f27ec4bd5ff9a109d4309deed31b45ae5a7216bacc6644759fac82d"
|
||||
],
|
||||
"version": "==8.10.8"
|
||||
},
|
||||
"pickleshare": {
|
||||
"hashes": [
|
||||
"sha256:92ee3b0e21632542ecc9a0a245e69a126f62e5114081bdb0d32e0edd10410033",
|
||||
"sha256:b58cf7d70658a091621c0d8cc35143c8569f3827496b27ed896918c237d05d96"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.7.2"
|
||||
},
|
||||
"pillow": {
|
||||
"hashes": [
|
||||
"sha256:051de330a06c99d6f84bcf582960487835bcae3fc99365185dc2d4f65a390c0e",
|
||||
"sha256:0ae5289948c5e0a16574750021bd8be921c27d4e3527800dc9c2c1d2abc81bf7",
|
||||
"sha256:0b1efce03619cdbf8bcc61cfae81fcda59249a469f31c6735ea59badd4a6f58a",
|
||||
"sha256:163136e09bd1d6c6c6026b0a662976e86c58b932b964f255ff384ecc8c3cefa3",
|
||||
"sha256:18e912a6ccddf28defa196bd2021fe33600cbe5da1aa2f2e2c6df15f720b73d1",
|
||||
"sha256:24ec3dea52339a610d34401d2d53d0fb3c7fd08e34b20c95d2ad3973193591f1",
|
||||
"sha256:267f8e4c0a1d7e36e97c6a604f5b03ef58e2b81c1becb4fccecddcb37e063cc7",
|
||||
"sha256:3273a28734175feebbe4d0a4cde04d4ed20f620b9b506d26f44379d3c72304e1",
|
||||
"sha256:4c678e23006798fc8b6f4cef2eaad267d53ff4c1779bd1af8725cc11b72a63f3",
|
||||
"sha256:4d4bc2e6bb6861103ea4655d6b6f67af8e5336e7216e20fff3e18ffa95d7a055",
|
||||
"sha256:505738076350a337c1740a31646e1de09a164c62c07db3b996abdc0f9d2e50cf",
|
||||
"sha256:5233664eadfa342c639b9b9977190d64ad7aca4edc51a966394d7e08e7f38a9f",
|
||||
"sha256:5d95cb9f6cced2628f3e4de7e795e98b2659dfcc7176ab4a01a8b48c2c2f488f",
|
||||
"sha256:7eda4c737637af74bac4b23aa82ea6fbb19002552be85f0b89bc27e3a762d239",
|
||||
"sha256:801ddaa69659b36abf4694fed5aa9f61d1ecf2daaa6c92541bbbbb775d97b9fe",
|
||||
"sha256:825aa6d222ce2c2b90d34a0ea31914e141a85edefc07e17342f1d2fdf121c07c",
|
||||
"sha256:9c215442ff8249d41ff58700e91ef61d74f47dfd431a50253e1a1ca9436b0697",
|
||||
"sha256:a3d90022f2202bbb14da991f26ca7a30b7e4c62bf0f8bf9825603b22d7e87494",
|
||||
"sha256:a631fd36a9823638fe700d9225f9698fb59d049c942d322d4c09544dc2115356",
|
||||
"sha256:a6523a23a205be0fe664b6b8747a5c86d55da960d9586db039eec9f5c269c0e6",
|
||||
"sha256:a756ecf9f4b9b3ed49a680a649af45a8767ad038de39e6c030919c2f443eb000",
|
||||
"sha256:b117287a5bdc81f1bac891187275ec7e829e961b8032c9e5ff38b70fd036c78f",
|
||||
"sha256:ba04f57d1715ca5ff74bb7f8a818bf929a204b3b3c2c2826d1e1cc3b1c13398c",
|
||||
"sha256:cd878195166723f30865e05d87cbaf9421614501a4bd48792c5ed28f90fd36ca",
|
||||
"sha256:cee815cc62d136e96cf76771b9d3eb58e0777ec18ea50de5cfcede8a7c429aa8",
|
||||
"sha256:d1722b7aa4b40cf93ac3c80d3edd48bf93b9208241d166a14ad8e7a20ee1d4f3",
|
||||
"sha256:d7c1c06246b05529f9984435fc4fa5a545ea26606e7f450bdbe00c153f5aeaad",
|
||||
"sha256:e9c8066249c040efdda84793a2a669076f92a301ceabe69202446abb4c5c5ef9",
|
||||
"sha256:f227d7e574d050ff3996049e086e1f18c7bd2d067ef24131e50a1d3fe5831fbc",
|
||||
"sha256:fc9a12aad714af36cf3ad0275a96a733526571e52710319855628f476dcb144e"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==5.4.1"
|
||||
},
|
||||
"psycopg2-binary": {
|
||||
"hashes": [
|
||||
"sha256:036bcb198a7cc4ce0fe43344f8c2c9a8155aefa411633f426c8c6ed58a6c0426",
|
||||
"sha256:1d770fcc02cdf628aebac7404d56b28a7e9ebec8cfc0e63260bd54d6edfa16d4",
|
||||
"sha256:1fdc6f369dcf229de6c873522d54336af598b9470ccd5300e2f58ee506f5ca13",
|
||||
"sha256:21f9ddc0ff6e07f7d7b6b484eb9da2c03bc9931dd13e36796b111d631f7135a3",
|
||||
"sha256:247873cda726f7956f745a3e03158b00de79c4abea8776dc2f611d5ba368d72d",
|
||||
"sha256:3aa31c42f29f1da6f4fd41433ad15052d5ff045f2214002e027a321f79d64e2c",
|
||||
"sha256:475f694f87dbc619010b26de7d0fc575a4accf503f2200885cc21f526bffe2ad",
|
||||
"sha256:4b5e332a24bf6e2fda1f51ca2a57ae1083352293a08eeea1fa1112dc7dd542d1",
|
||||
"sha256:570d521660574aca40be7b4d532dfb6f156aad7b16b5ed62d1534f64f1ef72d8",
|
||||
"sha256:59072de7def0690dd13112d2bdb453e20570a97297070f876fbbb7cbc1c26b05",
|
||||
"sha256:5f0b658989e918ef187f8a08db0420528126f2c7da182a7b9f8bf7f85144d4e4",
|
||||
"sha256:649199c84a966917d86cdc2046e03d536763576c0b2a756059ae0b3a9656bc20",
|
||||
"sha256:6645fc9b4705ae8fbf1ef7674f416f89ae1559deec810f6dd15197dfa52893da",
|
||||
"sha256:6872dd54d4e398d781efe8fe2e2d7eafe4450d61b5c4898aced7610109a6df75",
|
||||
"sha256:6ce34fbc251fc0d691c8d131250ba6f42fd2b28ef28558d528ba8c558cb28804",
|
||||
"sha256:73920d167a0a4d1006f5f3b9a3efce6f0e5e883a99599d38206d43f27697df00",
|
||||
"sha256:8a671732b87ae423e34b51139628123bc0306c2cb85c226e71b28d3d57d7e42a",
|
||||
"sha256:8d517e8fda2efebca27c2018e14c90ed7dc3f04d7098b3da2912e62a1a5585fe",
|
||||
"sha256:9475a008eb7279e20d400c76471843c321b46acacc7ee3de0b47233a1e3fa2cf",
|
||||
"sha256:96947b8cd7b3148fb0e6549fcb31258a736595d6f2a599f8cd450e9a80a14781",
|
||||
"sha256:abf229f24daa93f67ac53e2e17c8798a71a01711eb9fcdd029abba8637164338",
|
||||
"sha256:b1ab012f276df584beb74f81acb63905762c25803ece647016613c3d6ad4e432",
|
||||
"sha256:b22b33f6f0071fe57cb4e9158f353c88d41e739a3ec0d76f7b704539e7076427",
|
||||
"sha256:b3b2d53274858e50ad2ffdd6d97ce1d014e1e530f82ec8b307edd5d4c921badf",
|
||||
"sha256:bab26a729befc7b9fab9ded1bba9c51b785188b79f8a2796ba03e7e734269e2e",
|
||||
"sha256:daa1a593629aa49f506eddc9d23dc7f89b35693b90e1fbcd4480182d1203ea90",
|
||||
"sha256:dd111280ce40e89fd17b19c1269fd1b74a30fce9d44a550840e86edb33924eb8",
|
||||
"sha256:e0b86084f1e2e78c451994410de756deba206884d6bed68d5a3d7f39ff5fea1d",
|
||||
"sha256:eb86520753560a7e89639500e2a254bb6f683342af598088cb72c73edcad21e6",
|
||||
"sha256:ff18c5c40a38d41811c23e2480615425c97ea81fd7e9118b8b899c512d97c737"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.7.6.1"
|
||||
},
|
||||
"ptyprocess": {
|
||||
"hashes": [
|
||||
"sha256:0530ce63a9295bfae7bd06edc02b6aa935619f486f0f1dc0972f516265ee81a6",
|
||||
"sha256:464cb76f7a7122743dd25507650db89cd447c51f38e4671602b3eaa2e38e05ae"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.5.1"
|
||||
},
|
||||
"pycodestyle": {
|
||||
"hashes": [
|
||||
"sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766",
|
||||
"sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.3.1"
|
||||
},
|
||||
"pyexcel": {
|
||||
"hashes": [
|
||||
"sha256:2a32accc28aea3994922606ecf7bef00ef058b56b1bea6af119ae3bb56468333",
|
||||
"sha256:f1ffe613f09285edf42132b9afc14f81adbd0f56797e5fe05e98307d00ce175f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.5.10"
|
||||
},
|
||||
"pyexcel-io": {
|
||||
"hashes": [
|
||||
"sha256:55b2aa4ef81ba6e3285edfb1a3f3c3c69f9f4d52b6867318ae2381f88741143f",
|
||||
"sha256:de9de0d6bf9a8906c94e3b5dbe0b3a3e0a9bc893201d6a5c5b3cf84e5119d60d"
|
||||
],
|
||||
"version": "==0.5.16"
|
||||
},
|
||||
"pyexcel-xlsx": {
|
||||
"hashes": [
|
||||
"sha256:488783c3f5195bed8638f6064b11d97f706641b0f065a5416297a01db6cec5ea",
|
||||
"sha256:b3566162f7232336ebe0d40dd298145c18715009b020dddc210890cf6436ffb2"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.5.5"
|
||||
},
|
||||
"pyjwt": {
|
||||
"hashes": [
|
||||
"sha256:30b1380ff43b55441283cc2b2676b755cca45693ae3097325dea01f3d110628c",
|
||||
"sha256:4ee413b357d53fd3fb44704577afac88e72e878716116270d722723d65b42176"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.6.4"
|
||||
},
|
||||
"python-dateutil": {
|
||||
"hashes": [
|
||||
"sha256:3acbef017340600e9ff8f2994d8f7afd6eacb295383f286466a6df3961e486f0",
|
||||
"sha256:537bf2a8f8ce6f6862ad705cd68f9e405c0b5db014aa40fa29eab4335d4b1716",
|
||||
"sha256:62a2f8df3d66f878373fd0072eacf4ee52194ba302e00082828e0d263b0418d2"
|
||||
],
|
||||
"version": "==2.6.0"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:8781cdd3ca70f5a536884e051797ca213b9ff479a5c1cc57240adf37cc1eff1b",
|
||||
"sha256:be2ff04e94a2b5454ddcfbebb81ee8e46162734d4c2fcc90c422d16ab51f810b",
|
||||
"sha256:c823de61ff40d1996fe087cec343e0503881ca641b897e0f9b86c7683a0bfee1",
|
||||
"sha256:ee7c751544e35a7b7fb5e3fb25a49dade37d51e70a93e5107f10575d7102c311"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2016.4"
|
||||
},
|
||||
"pyyaml": {
|
||||
"hashes": [
|
||||
"sha256:1adecc22f88d38052fb787d959f003811ca858b799590a5eaa70e63dca50308c",
|
||||
"sha256:436bc774ecf7c103814098159fbb84c2715d25980175292c648f2da143909f95",
|
||||
"sha256:460a5a4248763f6f37ea225d19d5c205677d8d525f6a83357ca622ed541830c2",
|
||||
"sha256:5a22a9c84653debfbf198d02fe592c176ea548cccce47553f35f466e15cf2fd4",
|
||||
"sha256:7a5d3f26b89d688db27822343dfa25c599627bc92093e788956372285c6298ad",
|
||||
"sha256:9372b04a02080752d9e6f990179a4ab840227c6e2ce15b95e1278456664cf2ba",
|
||||
"sha256:a5dcbebee834eaddf3fa7366316b880ff4062e4bcc9787b78c7fbb4a26ff2dd1",
|
||||
"sha256:aee5bab92a176e7cd034e57f46e9df9a9862a71f8f37cad167c6fc74c65f5b4e",
|
||||
"sha256:c51f642898c0bacd335fc119da60baae0824f2cde95b0330b56c0553439f0673",
|
||||
"sha256:c68ea4d3ba1705da1e0d85da6684ac657912679a649e8868bd850d2c299cce13",
|
||||
"sha256:e23d0cc5299223dcc37885dae624f382297717e459ea24053709675a976a3e19"
|
||||
],
|
||||
"version": "==5.1"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:545c4855cd9d7c12671444326337013766f4eea6068c3f0307fb2dc2696d580e",
|
||||
"sha256:5acf980358283faba0b897c73959cecf8b841205bb4b2ad3ef545f46eae1a133"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.11.1"
|
||||
},
|
||||
"simplegeneric": {
|
||||
"hashes": [
|
||||
"sha256:dc972e06094b9af5b855b3df4a646395e43d1c9d0d39ed345b7393560d0b9173"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.8.1"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:0ff78c403d9bccf5a425a6d31a12aa6b47f1c21ca4dc2573a7e2f32a97335eb1",
|
||||
"sha256:105f8d68616f8248e24bf0e9372ef04d3cc10104f1980f54d57b2ce73a5ad56a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.10.0"
|
||||
},
|
||||
"tablib": {
|
||||
"hashes": [
|
||||
"sha256:0f88a9cebdaa1a2cc29ae57387082ee81015d1149ecd34e48a8c8d3b4dd21670",
|
||||
"sha256:5f33c079b07eb10cf9c4b4696add2ecf32c89db7729240546ecdcd5c92f67e13"
|
||||
],
|
||||
"version": "==0.13.0"
|
||||
},
|
||||
"telepot": {
|
||||
"hashes": [
|
||||
"sha256:8910fd6fb708e2c3ded7ca82cc945a645b717699d9f82ddff5123bb2e05f780f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==12.3"
|
||||
},
|
||||
"texttable": {
|
||||
"hashes": [
|
||||
"sha256:2b60a5304ccfbeac80ffae7350d7c2f5d7a24e9aab5036d0f82489746419d9b2"
|
||||
],
|
||||
"version": "==1.6.1"
|
||||
},
|
||||
"traitlets": {
|
||||
"hashes": [
|
||||
"sha256:05a66843c96a320eec09df674c16ff330a43cb07f731cf2bd88aa3645a180541",
|
||||
"sha256:76eba33c89723b8fc024f950cacaf5bf2ef37999642cc9a61f4e7c1ca5cf0ac0",
|
||||
"sha256:d6db3201395f9b955786d25a1817c07291e2bcb96eb7f41683ae3836836179d7"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.2.1"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39",
|
||||
"sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22"
|
||||
],
|
||||
"version": "==1.24.1"
|
||||
},
|
||||
"xlrd": {
|
||||
"hashes": [
|
||||
"sha256:546eb36cee8db40c3eaa46c351e67ffee6eeb5fa2650b71bc4c758a29a1b29b2",
|
||||
"sha256:e551fb498759fa3a5384a94ccd4c3c02eb7c00ea424426e212ac0c57be9dfbde"
|
||||
],
|
||||
"version": "==1.2.0"
|
||||
},
|
||||
"xlwt": {
|
||||
"hashes": [
|
||||
"sha256:a082260524678ba48a297d922cc385f58278b8aa68741596a87de01a9c628b2e",
|
||||
"sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88"
|
||||
],
|
||||
"version": "==1.3.0"
|
||||
},
|
||||
"yarl": {
|
||||
"hashes": [
|
||||
"sha256:024ecdc12bc02b321bc66b41327f930d1c2c543fa9a561b39861da9388ba7aa9",
|
||||
"sha256:2f3010703295fbe1aec51023740871e64bb9664c789cba5a6bdf404e93f7568f",
|
||||
"sha256:3890ab952d508523ef4881457c4099056546593fa05e93da84c7250516e632eb",
|
||||
"sha256:3e2724eb9af5dc41648e5bb304fcf4891adc33258c6e14e2a7414ea32541e320",
|
||||
"sha256:5badb97dd0abf26623a9982cd448ff12cb39b8e4c94032ccdedf22ce01a64842",
|
||||
"sha256:73f447d11b530d860ca1e6b582f947688286ad16ca42256413083d13f260b7a0",
|
||||
"sha256:7ab825726f2940c16d92aaec7d204cfc34ac26c0040da727cf8ba87255a33829",
|
||||
"sha256:b25de84a8c20540531526dfbb0e2d2b648c13fd5dd126728c496d7c3fea33310",
|
||||
"sha256:c6e341f5a6562af74ba55205dbd56d248daf1b5748ec48a0200ba227bb9e33f4",
|
||||
"sha256:c9bb7c249c4432cd47e75af3864bc02d26c9594f49c82e2a28624417f0ae63b8",
|
||||
"sha256:e060906c0c585565c718d1c3841747b61c5439af2211e185f6739a9412dfbde1"
|
||||
],
|
||||
"version": "==1.3.0"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"autopep8": {
|
||||
"hashes": [
|
||||
"sha256:33d2b5325b7e1afb4240814fe982eea3a92ebea712869bfd08b3c0393404248c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.4.3"
|
||||
},
|
||||
"entrypoints": {
|
||||
"hashes": [
|
||||
"sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19",
|
||||
"sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"
|
||||
],
|
||||
"version": "==0.3"
|
||||
},
|
||||
"flake8": {
|
||||
"hashes": [
|
||||
"sha256:859996073f341f2670741b51ec1e67a01da142831aa1fdc6242dbf88dffbe661",
|
||||
"sha256:a796a115208f5c03b18f332f7c11729812c8c3ded6c46319c59b53efd3819da8"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.7.7"
|
||||
},
|
||||
"mccabe": {
|
||||
"hashes": [
|
||||
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
|
||||
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
|
||||
],
|
||||
"version": "==0.6.1"
|
||||
},
|
||||
"pycodestyle": {
|
||||
"hashes": [
|
||||
"sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766",
|
||||
"sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.3.1"
|
||||
},
|
||||
"pyflakes": {
|
||||
"hashes": [
|
||||
"sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0",
|
||||
"sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"
|
||||
],
|
||||
"version": "==2.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
# Web 2.0 Backend
|
||||
|
||||
[Django](https://www.djangoproject.com/) backend containing multiple small applications and api for Next.js frontend.
|
||||
|
||||
* **Web app:** Backend for the main website.
|
||||
* **Member register:** Data table app for viewing and modifying the member register, member applications and membership payments.
|
||||
* **Kaehmy:** Form for creating and listing kaehmys
|
||||
* **Ohlhafv:** Form for creating and listing ohlhafv challenges.
|
||||
* **Infoscreen:** Angular-based slideshow app for the guild room's screens.
|
||||
|
||||
## Installation
|
||||
|
||||
Set up your SSH key authentication in GitLab Profile Settings. Then clone the repository and checkout the development branch:
|
||||
|
||||
```bash
|
||||
git clone git@gitlab.com:sahkoinsinoorikilta/vtmk/web2.0-backend.git
|
||||
cd web2.0-backend
|
||||
git checkout develop
|
||||
```
|
||||
|
||||
Copy env file for local use:
|
||||
|
||||
```bash
|
||||
cp .env.dev .env
|
||||
```
|
||||
|
||||
### Poetry
|
||||
|
||||
For depedencies and virtual environment, we use [poetry](https://python-poetry.org/).
|
||||
|
||||
First install [python](https://wiki.python.org/moin/BeginnersGuide/Download). Then install poetry:
|
||||
|
||||
```bash
|
||||
python -m pip install poetry==2.0.1
|
||||
```
|
||||
|
||||
The easiest integration with VSCode is to have poetry install virtual environment in project folder, configured with CMD
|
||||
|
||||
```bash
|
||||
python -m poetry config virtualenvs.in-project true
|
||||
```
|
||||
|
||||
### Node
|
||||
|
||||
We use Node.js for few development tasks, like linting. Easiest way to install Node is [nvm](https://github.com/nvm-sh/nvm). After installing install dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
TODO: List scripts
|
||||
|
||||
### Database
|
||||
|
||||
To run a local development database **[docker](https://docs.docker.com/engine/install/)** is recommended. If you want to additianally use a db management tool **[pgAdmin](https://www.pgadmin.org/download/)** is nice.
|
||||
|
||||
After installing docker use the following to create a database:
|
||||
|
||||
```bash
|
||||
docker run --name sik.web.db -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres:12
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
Activate virtual environment in shell
|
||||
|
||||
```bash
|
||||
eval $(python -m poetry env activate)
|
||||
```
|
||||
|
||||
Install dependencies
|
||||
|
||||
```bash
|
||||
poetry install
|
||||
```
|
||||
|
||||
### Initializing data
|
||||
|
||||
Run the following `manage.py` commands to initialize a new database. Do not run these in production without thinking!
|
||||
|
||||
```bash
|
||||
python manage.py migrate # run migrations
|
||||
python manage.py createdefaultadmin # creates an admin user
|
||||
python manage.py initialize # creates user groups
|
||||
python manage.py createdummydata # creates dummy members to the member register
|
||||
```
|
||||
|
||||
### Running
|
||||
|
||||
```bash
|
||||
python manage.py runserver
|
||||
```
|
||||
|
||||
Visit [https://localhost:8000](https://localhost:8000) in your browser!
|
||||
|
||||
Using address `0.0.0.0` will bind to all IP addresses. Using `localhost` will only bind to your machine.
|
||||
|
||||
```bash
|
||||
python manage.py runserver 0.0.0.0:8000
|
||||
```
|
||||
|
||||
### Development workflow
|
||||
|
||||
When you start working on a feature, create a feature branch for your changes. These feature branches should be prefixed with `feature`.
|
||||
|
||||
Example of creating a feature branch:
|
||||
|
||||
```bash
|
||||
git checkout -b feature-branch-name
|
||||
```
|
||||
|
||||
When your changes are ready and the code works without errors, submit a merge request to `develop` in GitLab. Another developer reviews your changes and runs the merge. Feature branches should be closed on merge.
|
||||
|
||||
Bugfixes do not need their own feature branches and can be pushed straight to `develop`, but if the fix needs a notable amount of work, it should be done in a `bugfix` branch instead.
|
||||
|
||||
Merge requests to `master` should be reviewed by multiple developers. Only a moderator can accept merge requests to `master`.
|
||||
|
||||
### Linting
|
||||
|
||||
Lint python files using `black` with
|
||||
|
||||
```bash
|
||||
npm run lint:py # check changes
|
||||
npm run lint:py:fix # fix errors
|
||||
```
|
||||
|
||||
Lint javascript and markdown using `eslint` and `remark` with
|
||||
|
||||
```bash
|
||||
npm run lint:md # markdown
|
||||
npm run lint:js # javascript
|
||||
```
|
||||
|
||||
Use an editor with linting capabilities to write pretty code that passes linting. Examples include _VSCode_, _Atom_ and _Pycharm_.
|
||||
|
||||
### Unit tests
|
||||
|
||||
Run unit tests with
|
||||
|
||||
```bash
|
||||
python manage.py test
|
||||
```
|
||||
|
||||
Due to the mostly static nature of the project, most elements are difficult to properly unit test. If you write code with actual logic, make sure to write at least one unit or integration test that tests your code's core functionality.
|
||||
|
||||
Tests are located in `tests.py` under every subproject.
|
||||
|
||||
## Production
|
||||
|
||||
Project is run in production with Docker. See `Dockerfile` for details.
|
||||
|
||||
For more information about deployment check **[infra](https://gitlab.com/sahkoinsinoorikilta/vtmk/infra)** repository.
|
||||
|
||||
## GitLab CI
|
||||
|
||||
All pushed changes go through the GitLab Continuous Integration, which consists of automated unit testing and linting. Make sure your changes pass both before merging to `develop` or `master`.
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
@@ -0,0 +1,124 @@
|
||||
body {
|
||||
background-color: white;
|
||||
font-family: monospace;
|
||||
color: black;
|
||||
}
|
||||
#container{
|
||||
position:relative;
|
||||
width:95%;
|
||||
margin-left:auto;
|
||||
margin-right:auto;
|
||||
height:100%;
|
||||
overflow:hidden;
|
||||
|
||||
}
|
||||
#upper{
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: bottom center;
|
||||
background-image: url("/static/coffee_scale/img/smokes.png");
|
||||
transform-origin: bottom;
|
||||
animation: smokes 8s ease-in-out 0s infinite;
|
||||
opacity:0;
|
||||
height:40%;
|
||||
}
|
||||
#lower{
|
||||
position:relative;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: top center;
|
||||
background-image: url("/static/coffee_scale/img/coffeecup3.png");
|
||||
height:60%;
|
||||
}
|
||||
#scale{
|
||||
position:absolute;
|
||||
top:80%;
|
||||
width:90%;
|
||||
height:10%;
|
||||
margin: 0% 5% 0% 5%;
|
||||
background: lightgrey;
|
||||
border-radius: 10px;
|
||||
overflow:hidden;
|
||||
}
|
||||
#scale2{
|
||||
width: 0%;
|
||||
transition: width 2s;
|
||||
height:100%;
|
||||
background: green;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.brewtime{
|
||||
text-align:right;
|
||||
position:absolute;
|
||||
right:0px;
|
||||
z-index:5;
|
||||
font-size:10vw;
|
||||
}
|
||||
#address{
|
||||
text-align:left;
|
||||
position:absolute;
|
||||
left:0px;
|
||||
z-index:5;
|
||||
font-size:4vw;
|
||||
color: #333;
|
||||
}
|
||||
.layertwo{
|
||||
display: None;
|
||||
}
|
||||
noscript{
|
||||
color:red;
|
||||
}
|
||||
.text{
|
||||
color:green;
|
||||
position:absolute;
|
||||
top:50%;
|
||||
left:50%;
|
||||
}
|
||||
.brewing{
|
||||
animation: brewing 5s ease-in-out 0s infinite;
|
||||
}
|
||||
.hurry{
|
||||
color:red !important;
|
||||
}
|
||||
.unknown{
|
||||
color:orange !important;
|
||||
animation: unknown 5s ease-in-out 0s infinite;
|
||||
}
|
||||
.friday{
|
||||
animation: friday 20s ease-in-out 0s infinite;
|
||||
}
|
||||
.normal{
|
||||
animation: normal 1000s ease-in-out 0s infinite;
|
||||
}
|
||||
.coffeeready{
|
||||
animation: coffeeready 10s ease-in-out 0s;
|
||||
}
|
||||
@keyframes smokes {
|
||||
0% {transform: skewX(-10deg);}
|
||||
50% {transform: skewX(10deg);}
|
||||
100% {transform: skewX(-10deg);}
|
||||
}
|
||||
@keyframes brewing {
|
||||
0% {color:green;}
|
||||
50% {color: transparent;}
|
||||
100% {color:green;}
|
||||
}
|
||||
@keyframes coffeeready {
|
||||
0% {background-color:white;}
|
||||
25% {background-color:rgb(100, 255, 100);}
|
||||
50% {background-color:white;}
|
||||
75% {background-color:rgb(100, 255, 100);}
|
||||
100% {background-color:white;}
|
||||
}
|
||||
@keyframes unknown {
|
||||
0%,40% {transform: rotate(0deg);}
|
||||
60%,100% {transform: rotate(360deg);}
|
||||
}
|
||||
@keyframes friday {
|
||||
0% {transform: rotate(0deg);}
|
||||
100% {transform: rotate(360deg);}
|
||||
}
|
||||
@keyframes normal {
|
||||
0%,49% {transform: rotate(0deg);}
|
||||
50%,100% {transform: rotate(360deg);}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.0 KiB |
@@ -0,0 +1,183 @@
|
||||
//Inner state
|
||||
var lastBrew = new Date(0);
|
||||
var brewing = false;
|
||||
var backoff = 2000;
|
||||
|
||||
//MQTT client config
|
||||
var username = "coffee-user-"+ Math.random();
|
||||
// eslint-disable-next-line no-undef
|
||||
var client = new Paho.MQTT.Client("sika.sahkoinsinoorikilta.fi", 9001, username);
|
||||
client.onMessageArrived = function (message) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("Topic: "+message.destinationName+" msg: "+message.payloadString);
|
||||
var ev = new CustomEvent(message.destinationName, {'detail': message.payloadString});
|
||||
window.dispatchEvent(ev);
|
||||
}
|
||||
|
||||
function reconnect(responseObject){
|
||||
if (responseObject.errorCode !== 0) {
|
||||
console.log("connection lost! Reason: "+responseObject.errorMessage); // eslint-disable-line no-console
|
||||
setTimeout(function(){
|
||||
client.connect({onSuccess:onConnect, useSSL:true, onFailure: reconnect});
|
||||
}, backoff);
|
||||
}
|
||||
}
|
||||
|
||||
function onConnect() {
|
||||
console.log("MQTT connected"); // eslint-disable-line no-console
|
||||
//set and reset reconnector
|
||||
client.onConnectionLost = reconnect
|
||||
// subscribe to topics
|
||||
client.subscribe("sik/kiltahuone/kahvivaaka/cups");
|
||||
client.subscribe("sik/kiltahuone/kahvivaaka/brewing");
|
||||
client.subscribe("sik/kiltahuone/kahvivaaka/brewtime");
|
||||
}
|
||||
|
||||
// data update and parse functions
|
||||
function parseCups(ev){
|
||||
var cups = parseFloat(ev.detail).toFixed(1)
|
||||
|
||||
function makeEvent(cups) {
|
||||
return (String(cups) !== '-1.0')
|
||||
? new CustomEvent("cupsChanged", {'detail': cups})
|
||||
: new CustomEvent("cupsError", {'detail': 'Error: unable to fetch cups :('});
|
||||
}
|
||||
|
||||
window.dispatchEvent(makeEvent(cups));
|
||||
}
|
||||
function updateCups(ev){
|
||||
$("#text").text(ev.detail);
|
||||
}
|
||||
function showCupsError(ev) {
|
||||
$('#text').text(ev.detail);
|
||||
$('#text').css({
|
||||
'font-size': '7vh',
|
||||
'left': '0',
|
||||
'top': '40%',
|
||||
'width': '100%',
|
||||
'text-align': 'center',
|
||||
'color': 'red',
|
||||
});
|
||||
$('#lower').css({'background-image': 'none'});
|
||||
}
|
||||
function updateScale(ev){
|
||||
$("#scale2").css({width: Math.min(ev.detail/9*100,100) + '%'});
|
||||
}
|
||||
|
||||
function tick(){
|
||||
var ev = new CustomEvent("tick", {'detail': new Date()});
|
||||
window.dispatchEvent(ev);
|
||||
}
|
||||
|
||||
function updateTime(ev){
|
||||
var now = ev.detail;
|
||||
$("#time").html(formatTime(now.getHours(),now.getMinutes(),now.getSeconds()));
|
||||
}
|
||||
|
||||
function coffeeLowEffect(ev){
|
||||
ev.detail <= 2 ? $("#text").addClass("hurry") : $("#text").removeClass("hurry");
|
||||
}
|
||||
function coffeeReadyEffect(){
|
||||
$("body").addClass("coffeeready");
|
||||
// autoclear animation class in 10s
|
||||
setTimeout(function(){$("body").removeClass("coffeeready");}, 10000);
|
||||
}
|
||||
function hotEffect(ev){
|
||||
var opa = Math.max(100 - ev.detail / 90000,0);
|
||||
$("#upper").css({opacity: opa/100});
|
||||
}
|
||||
function brewAnimStart(){
|
||||
$(".text").addClass("brewing");
|
||||
$(".layerone").hide();
|
||||
$(".layertwo").show();
|
||||
}
|
||||
function brewAnimEnd(){
|
||||
$(".text").removeClass("brewing");
|
||||
$(".layertwo").hide();
|
||||
$(".layerone").show();
|
||||
}
|
||||
function brewNotifier(ev){
|
||||
var new_brewing = parseInt(ev.detail);
|
||||
if (new_brewing == 1 && brewing == 0){
|
||||
window.dispatchEvent(new Event("brewStart"));
|
||||
} else if (new_brewing == 0 && brewing == 1){
|
||||
window.dispatchEvent(new Event("brewEnd"));
|
||||
}
|
||||
brewing = new_brewing;
|
||||
}
|
||||
function brewTimeParser(ev){
|
||||
lastBrew = new Date(parseInt(ev.detail)*1000.0);
|
||||
}
|
||||
function updateBrewDiff(){
|
||||
var now = new Date();
|
||||
var timeDiff = Math.max(now.getTime() - lastBrew.getTime(), 0);
|
||||
var eve = new CustomEvent("dtUpdate", {'detail': timeDiff});
|
||||
window.dispatchEvent(eve);
|
||||
}
|
||||
function updateBrewTime(ev){
|
||||
var timeDiff = ev.detail;
|
||||
var timeStr;
|
||||
if (timeDiff < 3600000){
|
||||
timeStr = Math.round(timeDiff / 60000) + ' min'
|
||||
} else if (timeDiff < 10000* 3600 * 1000){ // 1000h
|
||||
timeStr = '~' + Math.round(timeDiff / 3600000 * 2) / 2 + ' h';
|
||||
} else {
|
||||
timeStr = "???"
|
||||
}
|
||||
$("#brewtime").html(timeStr);
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
function nToS(num){
|
||||
return num < 10 ? "0" + num : "" + num;
|
||||
}
|
||||
|
||||
function formatTime(hours, minutes, seconds){
|
||||
return nToS(hours)+":"+nToS(minutes)+":"+nToS(seconds)
|
||||
}
|
||||
|
||||
function resize(){
|
||||
var w = $("#container").width();
|
||||
var h = $("#container").height();
|
||||
var s = w > h ? h : w;
|
||||
var font = s * 0.8 * 0.38/Math.sqrt(3);
|
||||
$(".text").css({ top: s*0.16-font/2 + 'px',
|
||||
fontSize: font + 'px',
|
||||
marginLeft: -font*3*3/10 + 'px'});
|
||||
}
|
||||
|
||||
// Init everything
|
||||
|
||||
$(document).ready(function(){
|
||||
client.connect({onSuccess:onConnect, useSSL:true, onFailure:reconnect});
|
||||
|
||||
//connect MQTT event listeners
|
||||
window.addEventListener("sik/kiltahuone/kahvivaaka/cups", parseCups);
|
||||
window.addEventListener("sik/kiltahuone/kahvivaaka/brewing", brewNotifier);
|
||||
window.addEventListener("sik/kiltahuone/kahvivaaka/brewtime", brewTimeParser);
|
||||
|
||||
//connect other event listeners
|
||||
window.addEventListener("cupsChanged", updateCups);
|
||||
window.addEventListener("cupsChanged", coffeeLowEffect);
|
||||
window.addEventListener("cupsChanged", updateScale);
|
||||
window.addEventListener("cupsChanged", resize);
|
||||
|
||||
window.addEventListener("cupsError", showCupsError);
|
||||
window.addEventListener("cupsError", coffeeLowEffect);
|
||||
window.addEventListener("cupsError", updateScale);
|
||||
|
||||
window.addEventListener("brewStart", brewAnimStart);
|
||||
window.addEventListener("brewEnd", brewAnimEnd);
|
||||
window.addEventListener("brewEnd", coffeeReadyEffect);
|
||||
window.addEventListener("tick", updateTime);
|
||||
window.addEventListener("tick", updateBrewDiff);
|
||||
window.addEventListener("dtUpdate", updateBrewTime);
|
||||
window.addEventListener("dtUpdate", hotEffect);
|
||||
|
||||
//start time based events
|
||||
setInterval(tick, 100);
|
||||
tick();
|
||||
|
||||
});
|
||||
$(window).resize(resize);
|
||||
@@ -0,0 +1,41 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Coffee Cups @Guild Room - AYY SIK ry</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="3600">
|
||||
<meta http-equiv="cache-control" content="max-age=0" />
|
||||
<meta http-equiv="cache-control" content="no-cache" />
|
||||
<meta http-equiv="expires" content="0" />
|
||||
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
|
||||
<meta http-equiv="pragma" content="no-cache" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.2/mqttws31.js"
|
||||
type="text/javascript"></script>
|
||||
<link rel="stylesheet" href="{% static "coffee_scale/css/coffee.css" %}">
|
||||
<script src="{% static "coffee_scale/js/coffee.js" %}"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="container">
|
||||
<span id="brewtime" class="brewtime layerone"></span>
|
||||
<span class="brewtime layertwo">:)</span>
|
||||
<span id="address">
|
||||
ka.dy.fi
|
||||
<noscript><br>This page uses JavaScript!</noscript>
|
||||
<br><span id="time"></span></span>
|
||||
</span>
|
||||
<div id="upper">
|
||||
</div>
|
||||
<!--Kahvinkeitin on rikki. Varakeittimellä keitettyä kahvia saattaa olla.-->
|
||||
<div id="lower" class="normal">
|
||||
<div id="text" class="text layerone">???</div>
|
||||
<div class="text layertwo"> +</div>
|
||||
<div id="scale"><div id="scale2"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,2 @@
|
||||
from django.test import TestCase, Client
|
||||
from django.conf import settings
|
||||
@@ -0,0 +1,12 @@
|
||||
from django.conf.urls import url
|
||||
from django.conf import settings
|
||||
from .views import coffee_view
|
||||
|
||||
urlpatterns = [
|
||||
# landing page
|
||||
url(r'^$', coffee_view),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
urlpatterns += staticfiles_urlpatterns()
|
||||
@@ -0,0 +1,11 @@
|
||||
from django.shortcuts import render
|
||||
from django.http import JsonResponse
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
import logging
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
def coffee_view(request):
|
||||
return render(request, 'coffee_scale:coffee.html')
|
||||
+4
-13
@@ -1,22 +1,13 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:12
|
||||
volumes:
|
||||
- dbdata:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
|
||||
image: postgres
|
||||
web:
|
||||
build: .
|
||||
image: registry.gitlab.com/sahkoinsinoorikilta/vtmk/web2.0-backend
|
||||
env_file:
|
||||
- .env
|
||||
command: ["bash", "-c", "cd /code && ./wait-for-it.sh db:5432 -- bash setup.sh --no-input --no-npm && python manage.py runserver 0.0.0.0:8000"]
|
||||
ports:
|
||||
- "8000:8000"
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
volumes:
|
||||
dbdata:
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import globals from "globals";
|
||||
import js from "@eslint/js";
|
||||
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ["**/.venv/", "**/collected_static/", "**/static/js/lib/**"],
|
||||
},
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.jquery,
|
||||
angular: true,
|
||||
moment: true,
|
||||
_: true
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
...js.configs.recommended
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ExpensesClaimConfig(AppConfig):
|
||||
name = 'expenses_claim'
|
||||
@@ -0,0 +1,32 @@
|
||||
"""Expenses claim form."""
|
||||
|
||||
from django import forms
|
||||
from string import ascii_uppercase
|
||||
|
||||
|
||||
class ExpensesClaim(forms.Form):
|
||||
"""Expenses claim form."""
|
||||
|
||||
name = forms.CharField(label='Nimi', max_length=100)
|
||||
iban = forms.CharField(label='IBAN', max_length=100)
|
||||
email = forms.EmailField(label='Sähköposti', max_length=100)
|
||||
amount = forms.DecimalField(label='Yhteensä', decimal_places=2)
|
||||
|
||||
def clean_iban(self):
|
||||
"""Validate IBAN."""
|
||||
orig_iban = self.cleaned_data['iban']
|
||||
# Remove spaces.
|
||||
cleaned_iban = orig_iban.replace(' ', '')
|
||||
# Move first 4 symbols to the end of the string.
|
||||
cleaned_iban = cleaned_iban[4:] + cleaned_iban[0:4]
|
||||
LETTERS = {letter: str(index) for index,
|
||||
letter in enumerate(ascii_uppercase, start=10)}
|
||||
cleaned_iban = cleaned_iban.upper()
|
||||
# Replace all letters with numbers, so that A=10, B=11, ..., Z=35.
|
||||
cleaned_iban = [LETTERS[char] if char in LETTERS
|
||||
else char for char in cleaned_iban]
|
||||
cleaned_iban = ''.join(cleaned_iban)
|
||||
# If cleaned_iban modulo 97 != 1 the IBAN number is invalid.
|
||||
if int(cleaned_iban) % 97 != 1:
|
||||
raise forms.ValidationError('Invalid IBAN number!')
|
||||
return orig_iban
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
@@ -0,0 +1,169 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.heading {
|
||||
padding: 20px 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.claim-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 20px;
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
.table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: space-between;
|
||||
padding: 20px 10px;
|
||||
}
|
||||
|
||||
.scrollarea {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.tablerow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
padding: 10px 5px;
|
||||
}
|
||||
|
||||
.fieldscolumn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.fieldsrow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: stretch;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.submit {
|
||||
align-self: center;
|
||||
width: 30%;
|
||||
margin: 20px 10px;
|
||||
}
|
||||
|
||||
.imageupload > input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.fieldsrow > div {
|
||||
margin: 10px 20px;
|
||||
}
|
||||
|
||||
.fieldscolumn > div {
|
||||
margin: 10px 20px;
|
||||
}
|
||||
|
||||
|
||||
.fieldscolumn > div > label {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.money > input {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
#addreceipt {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
#total {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
padding: 20px 10px;
|
||||
}
|
||||
|
||||
#receiptslist {
|
||||
height: 20rem;
|
||||
}
|
||||
|
||||
#info2 {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
#textbox > textarea {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
#id_iban {
|
||||
width: 18rem;
|
||||
}
|
||||
|
||||
|
||||
.fieldscolumn > #textbox > textarea {
|
||||
height: 8rem;
|
||||
width: 18rem;
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.heading {
|
||||
font-size: 18px;
|
||||
padding: 5px 5px;
|
||||
}
|
||||
|
||||
h1.heading {
|
||||
visibility: hidden;
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
.table {
|
||||
padding: 5px 5px
|
||||
}
|
||||
|
||||
#total {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.fieldsrow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
padding: 5px 5px
|
||||
}
|
||||
|
||||
#receiptslist {
|
||||
width: 12rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-height: 741px) {
|
||||
#receiptslist {
|
||||
height: 17rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-height: 569px) {
|
||||
#receiptslist {
|
||||
height: 10rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
function addReceipt(event) {
|
||||
event.preventDefault();
|
||||
var receipts = document.getElementById("receiptslist");
|
||||
var div2append = document.createElement("div");
|
||||
var newrow = receipts.appendChild(div2append);
|
||||
newrow.classList.add("tablerow");
|
||||
|
||||
div2append = document.createElement("div");
|
||||
var div1 = newrow.appendChild(div2append);
|
||||
div1.classList.add("imageupload");
|
||||
div2append = document.createElement("div");
|
||||
var div2 = newrow.appendChild(div2append);
|
||||
div2.classList.add("desc");
|
||||
div2append = document.createElement("div");
|
||||
var div3 = newrow.appendChild(div2append);
|
||||
div3.classList.add("money");
|
||||
|
||||
var file = document.getElementById("newreceipt");
|
||||
var desc = document.getElementById("adddescription");
|
||||
var sum = document.getElementById("addsum");
|
||||
div1.appendChild(file.cloneNode(true));
|
||||
div2.appendChild(document.createTextNode(desc.value));
|
||||
div3.appendChild(document.createTextNode(sum.value));
|
||||
|
||||
var total = document.getElementById("totalsum");
|
||||
total.value = Number(total.value) + Number(sum.value);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{% extends "project.html" %}
|
||||
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block styles %}
|
||||
<script src="{% static "claim_form/js/addReceipt.js" %}"></script>
|
||||
<link rel="stylesheet" href="{% static "claim_form/css/base.css" %}"></script>
|
||||
{% endblock styles %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
<div class="claim-form">
|
||||
{% block content %}
|
||||
{% include "expenses_claim:form.html" %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
||||
{% endblock body %}
|
||||
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Raha-anomus</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Raha-anomus</h1>
|
||||
<div>
|
||||
<div>
|
||||
{{ name }}
|
||||
</div>
|
||||
<div>
|
||||
{{ iban }}
|
||||
</div>
|
||||
<div>
|
||||
{{ amount }}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,72 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% block content %}
|
||||
|
||||
<form class="form" id="claim" action="" method="post">
|
||||
<h1 class="heading">Aalto Yliopiston Sähköinsinöörikilta ry Raha-anomuslomake</h1>
|
||||
<div class="fieldsrow">
|
||||
<div class="imageupload" id="newreceipt">
|
||||
<label for="newreceipt">
|
||||
Kuitti
|
||||
<img class="thumbnail" src="{% static "claim_form/img/icon.png" %}"/>
|
||||
</label>
|
||||
<input id="selectreceipt" type="file"/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="adddescription">
|
||||
Kuvaus
|
||||
</label>
|
||||
<input id="adddescription" type="text">
|
||||
</div>
|
||||
<div class="money">
|
||||
<label for="addsum">
|
||||
Summa
|
||||
</label>
|
||||
<input type="number" id="addsum" maxlength=100/>
|
||||
</div>
|
||||
<div>
|
||||
<button id="addreceipt" onclick="addReceipt(event)">Lisää kuitti</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table">
|
||||
<div class="scrollarea" id="receiptslist">
|
||||
<div class="tablerow">
|
||||
<div class="tableelement">Kuitti</div>
|
||||
<div class="tableelement">Kuvaus</div>
|
||||
<div class="tableelement">Summa</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="money" id="total">
|
||||
<label for="id_amount">Yhteensä</label>
|
||||
{{ form.amount }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="fieldscolumn">
|
||||
<div class="fieldsrow">
|
||||
<div class="fieldscolumn" id="info1">
|
||||
<div id="textbox">
|
||||
<label for="selite">Selite</label>
|
||||
<textarea id="selite"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label for="id_iban">{{ form.iban.label }}</label>
|
||||
{{ form.iban }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="fieldscolumn" id="info2">
|
||||
<div>
|
||||
<label for="id_name">{{ form.name.label }}</label>
|
||||
{{ form.name }}
|
||||
</div>
|
||||
<div>
|
||||
<label for="id_email">{{ form.email.label }}</label>
|
||||
{{ form.email }}
|
||||
</div>
|
||||
<input class="submit" id="submitclaim" type="submit" value="Submit"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% csrf_token %}
|
||||
</form>
|
||||
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,60 @@
|
||||
from django.test import TestCase
|
||||
from .forms import ExpensesClaim
|
||||
|
||||
|
||||
class ExpensesClaimTest(TestCase):
|
||||
"""Test expenses claim form."""
|
||||
|
||||
def test_valid_data1(self):
|
||||
form = ExpensesClaim({
|
||||
'name': "John Doe",
|
||||
'email': 'john@doe.com',
|
||||
'iban': "FI37 1590 3000 0007 76",
|
||||
'amount': 12.54
|
||||
})
|
||||
self.assertTrue(form.is_valid())
|
||||
|
||||
def test_valid_data2(self):
|
||||
form = ExpensesClaim({
|
||||
'name': "John Cena",
|
||||
'email': 'john@cena.com',
|
||||
'iban': "AL35202111090000000001234567",
|
||||
'amount': 12
|
||||
})
|
||||
self.assertTrue(form.is_valid())
|
||||
|
||||
def test_valid_data3(self):
|
||||
form = ExpensesClaim({
|
||||
'name': "John Wayne",
|
||||
'email': 'john@wayne.com',
|
||||
'iban': "BR1500000000000010932840814P2",
|
||||
'amount': 12.0
|
||||
})
|
||||
self.assertTrue(form.is_valid())
|
||||
|
||||
def test_invalid_iban(self):
|
||||
form = ExpensesClaim({
|
||||
'name': "John Lennon",
|
||||
'email': 'john@lennon.com',
|
||||
'iban': "FI3734 1590 3000 0007 76",
|
||||
'amount': 12.54
|
||||
})
|
||||
self.assertFalse(form.is_valid())
|
||||
|
||||
def test_invalid_amount(self):
|
||||
form = ExpensesClaim({
|
||||
'name': "John Kenedy",
|
||||
'email': 'john@kenedy.com',
|
||||
'iban': "FI37 1590 3000 0007 76",
|
||||
'amount': "asd"
|
||||
})
|
||||
self.assertFalse(form.is_valid())
|
||||
|
||||
def test_invalid_amount_decimal_places(self):
|
||||
form = ExpensesClaim({
|
||||
'name': "John Travolta",
|
||||
'email': 'john@travolta.com',
|
||||
'iban': "FI37 1590 3000 0007 76",
|
||||
'amount': 12.544
|
||||
})
|
||||
self.assertFalse(form.is_valid())
|
||||
@@ -0,0 +1,9 @@
|
||||
"""Expenses claim urls."""
|
||||
|
||||
from django.conf.urls import url
|
||||
from .views import claim
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^new', claim)
|
||||
]
|
||||
@@ -0,0 +1,41 @@
|
||||
"""Expenses claim views."""
|
||||
|
||||
from django.shortcuts import render
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django.http import HttpResponse
|
||||
from webapp.utils import send_email_with_attachment
|
||||
from django.template.loader import render_to_string
|
||||
from weasyprint import HTML
|
||||
from .forms import ExpensesClaim
|
||||
|
||||
|
||||
@require_http_methods(["GET", "POST"])
|
||||
def claim(request):
|
||||
"""Render expenses claim form."""
|
||||
|
||||
if request.method == 'POST':
|
||||
form = ExpensesClaim(request.POST)
|
||||
if form.is_valid():
|
||||
name = form.cleaned_data['name']
|
||||
amount = form.cleaned_data['amount']
|
||||
iban = form.cleaned_data['iban']
|
||||
html_string = render_to_string('expenses_claim:claim2pdf.html',
|
||||
{'name': name, 'iban': iban,
|
||||
'amount': amount}).encode('UTF-8')
|
||||
html = HTML(string=html_string)
|
||||
attachment = html.write_pdf()
|
||||
response = HttpResponse(
|
||||
attachment, content_type='application/pdf;'
|
||||
)
|
||||
response['Content-Disposition'] = 'filename=claim.pdf'
|
||||
|
||||
email = "leo.kivikunnas@aalto.fi"
|
||||
subject = "Test expenses claim"
|
||||
body = "Test"
|
||||
send_email_with_attachment(email, subject, body, attachment)
|
||||
return response
|
||||
|
||||
elif request.method == 'GET':
|
||||
form = ExpensesClaim()
|
||||
|
||||
return render(request, 'expenses_claim:base.html', {'form': form})
|
||||
+2
-9
@@ -2,15 +2,8 @@
|
||||
|
||||
from django.contrib import admin
|
||||
from infoscreen.models import (
|
||||
Rotation,
|
||||
InfoItem,
|
||||
InfoInstance,
|
||||
ImageInfoItem,
|
||||
ExternalImageInfoItem,
|
||||
ABBInfoItem,
|
||||
ExternalWebsiteInfoItem,
|
||||
VideoInfoItem,
|
||||
)
|
||||
Rotation, InfoItem, InfoInstance, ImageInfoItem,
|
||||
ExternalImageInfoItem, ABBInfoItem, ExternalWebsiteInfoItem, VideoInfoItem)
|
||||
|
||||
# Register your models here.
|
||||
admin.site.register(Rotation)
|
||||
|
||||
+1
-1
@@ -6,4 +6,4 @@ from django.apps import AppConfig
|
||||
class InfoscreenConfig(AppConfig):
|
||||
"""Infoscreen app configuration."""
|
||||
|
||||
name = "infoscreen"
|
||||
name = 'infoscreen'
|
||||
|
||||
@@ -11,173 +11,81 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("contenttypes", "0002_remove_content_type_name"),
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="HSLDataModel",
|
||||
name='HSLDataModel',
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("data", models.TextField(default="", editable=False)),
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('data', models.TextField(default='', editable=False)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="InfoInstance",
|
||||
name='InfoInstance',
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("duration", models.FloatField(default=15.0)),
|
||||
("item_id", models.PositiveIntegerField()),
|
||||
(
|
||||
"item_type",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="contenttypes.ContentType",
|
||||
),
|
||||
),
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('duration', models.FloatField(default=15.0)),
|
||||
('item_id', models.PositiveIntegerField()),
|
||||
('item_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="InfoItem",
|
||||
name='InfoItem',
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=255)),
|
||||
("expire_date", models.DateTimeField(blank=True, null=True)),
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('expire_date', models.DateTimeField(blank=True, null=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Rotation",
|
||||
name='Rotation',
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=255)),
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="ABBInfoItem",
|
||||
name='ABBInfoItem',
|
||||
fields=[
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
],
|
||||
bases=("infoscreen.infoitem",),
|
||||
bases=('infoscreen.infoitem',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="ExternalImageInfoItem",
|
||||
name='ExternalImageInfoItem',
|
||||
fields=[
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
("url", models.TextField()),
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
('url', models.TextField()),
|
||||
],
|
||||
bases=("infoscreen.infoitem",),
|
||||
bases=('infoscreen.infoitem',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="HslInfoItem",
|
||||
name='HslInfoItem',
|
||||
fields=[
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
],
|
||||
bases=("infoscreen.infoitem",),
|
||||
bases=('infoscreen.infoitem',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="ImageInfoItem",
|
||||
name='ImageInfoItem',
|
||||
fields=[
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
("img", models.ImageField(upload_to="infoimages/")),
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
('img', models.ImageField(upload_to='infoimages/')),
|
||||
],
|
||||
bases=("infoscreen.infoitem",),
|
||||
bases=('infoscreen.infoitem',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="SossoInfoItem",
|
||||
name='SossoInfoItem',
|
||||
fields=[
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
],
|
||||
bases=("infoscreen.infoitem",),
|
||||
bases=('infoscreen.infoitem',),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="infoinstance",
|
||||
name="rotation",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="instances",
|
||||
to="infoscreen.Rotation",
|
||||
),
|
||||
model_name='infoinstance',
|
||||
name='rotation',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='instances', to='infoscreen.Rotation'),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -9,25 +9,15 @@ import django.db.models.deletion
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("infoscreen", "0001_initial"),
|
||||
('infoscreen', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="CoffeeInfoItem",
|
||||
name='CoffeeInfoItem',
|
||||
fields=[
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
],
|
||||
bases=("infoscreen.infoitem",),
|
||||
bases=('infoscreen.infoitem',),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -9,63 +9,33 @@ import django.db.models.deletion
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("infoscreen", "0002_coffeeinfoitem"),
|
||||
('infoscreen', '0002_coffeeinfoitem'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="ApyInfoItem",
|
||||
name='ApyInfoItem',
|
||||
fields=[
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
],
|
||||
bases=("infoscreen.infoitem",),
|
||||
bases=('infoscreen.infoitem',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="EventInfoItem",
|
||||
name='EventInfoItem',
|
||||
fields=[
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
],
|
||||
bases=("infoscreen.infoitem",),
|
||||
bases=('infoscreen.infoitem',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="ExternalWebsiteInfoItem",
|
||||
name='ExternalWebsiteInfoItem',
|
||||
fields=[
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
("url", models.TextField()),
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
('url', models.TextField()),
|
||||
],
|
||||
bases=("infoscreen.infoitem",),
|
||||
bases=('infoscreen.infoitem',),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name="CoffeeInfoItem",
|
||||
name='CoffeeInfoItem',
|
||||
),
|
||||
]
|
||||
|
||||
@@ -9,26 +9,16 @@ import django.db.models.deletion
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("infoscreen", "0003_auto_20170329_1857"),
|
||||
('infoscreen', '0003_auto_20170329_1857'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="VideoInfoItem",
|
||||
name='VideoInfoItem',
|
||||
fields=[
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
("video", models.FileField(upload_to="infovideos/")),
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
('video', models.FileField(upload_to='infovideos/')),
|
||||
],
|
||||
bases=("infoscreen.infoitem",),
|
||||
bases=('infoscreen.infoitem',),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -8,18 +8,18 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("infoscreen", "0004_videoinfoitem"),
|
||||
('infoscreen', '0004_videoinfoitem'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="externalimageinfoitem",
|
||||
name="url",
|
||||
model_name='externalimageinfoitem',
|
||||
name='url',
|
||||
field=models.URLField(),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="externalwebsiteinfoitem",
|
||||
name="url",
|
||||
model_name='externalwebsiteinfoitem',
|
||||
name='url',
|
||||
field=models.URLField(),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -8,14 +8,14 @@ from django.db import migrations
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("infoscreen", "0005_auto_20170913_1841"),
|
||||
('infoscreen', '0005_auto_20170913_1841'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name="HSLDataModel",
|
||||
name='HSLDataModel',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name="HslInfoItem",
|
||||
name='HslInfoItem',
|
||||
),
|
||||
]
|
||||
|
||||
@@ -7,25 +7,15 @@ import django.db.models.deletion
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("infoscreen", "0006_delete_hsldatamodel"),
|
||||
('infoscreen', '0006_delete_hsldatamodel'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="LunchItem",
|
||||
name='LunchItem',
|
||||
fields=[
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
],
|
||||
bases=("infoscreen.infoitem",),
|
||||
bases=('infoscreen.infoitem',),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
]
|
||||
+54
-52
@@ -7,7 +7,7 @@ from django import forms
|
||||
from django.utils import timezone
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import gettext as _
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
|
||||
class InfoItem(models.Model):
|
||||
@@ -16,7 +16,6 @@ class InfoItem(models.Model):
|
||||
class __meta__:
|
||||
abstract = True
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = models.CharField(max_length=255)
|
||||
# expire_date = None means never expiring item
|
||||
expire_date = models.DateTimeField(blank=True, null=True)
|
||||
@@ -24,14 +23,14 @@ class InfoItem(models.Model):
|
||||
|
||||
def get_template_url(self):
|
||||
"""Get infoscreen template url."""
|
||||
raise NotImplementedError("inheriting classes must implement get_template_url")
|
||||
raise NotImplementedError(
|
||||
"inheriting classes must implement get_template_url")
|
||||
|
||||
@staticmethod
|
||||
def get_create_template_url():
|
||||
"""Get create infoscreen template url command."""
|
||||
raise NotImplementedError(
|
||||
"inheriting classes must implement get_create_template_url"
|
||||
)
|
||||
"inheriting classes must implement get_create_template_url")
|
||||
|
||||
@classmethod
|
||||
def create_from_dict(cls, d):
|
||||
@@ -43,13 +42,14 @@ class InfoItem(models.Model):
|
||||
def update_from_dict(self, d):
|
||||
"""Update model based on given dict."""
|
||||
try:
|
||||
expire_date = d.pop("expire_date", None)
|
||||
self.expire_date = datetime.strptime(expire_date, "%Y-%m-%d %H:%M:%S")
|
||||
expire_date = d.pop('expire_date', None)
|
||||
self.expire_date = datetime.strptime(
|
||||
expire_date, "%Y-%m-%d %H:%M:%S")
|
||||
except:
|
||||
pass
|
||||
|
||||
dmap = {
|
||||
"name": "name",
|
||||
'name': 'name',
|
||||
}
|
||||
for k, v in d.items():
|
||||
try:
|
||||
@@ -61,13 +61,13 @@ class InfoItem(models.Model):
|
||||
def get_dict(self):
|
||||
"""Convert django model to dict and return it."""
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"item_type": ContentType.objects.get_for_model(self).id,
|
||||
"template_url": self.get_template_url(),
|
||||
"display_name": self.display_name,
|
||||
"create_template_url": self.get_create_template_url(),
|
||||
"options": {},
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
'item_type': ContentType.objects.get_for_model(self).id,
|
||||
'template_url': self.get_template_url(),
|
||||
'display_name': self.display_name,
|
||||
'create_template_url': self.get_create_template_url(),
|
||||
'options': {}
|
||||
}
|
||||
|
||||
def delete(self):
|
||||
@@ -75,8 +75,8 @@ class InfoItem(models.Model):
|
||||
# since generic foreign keys suck, delete info
|
||||
# items pointing here manually
|
||||
InfoInstance.objects.filter(
|
||||
item_id=self.id, item_type=ContentType.objects.get_for_model(self)
|
||||
).delete()
|
||||
item_id=self.id,
|
||||
item_type=ContentType.objects.get_for_model(self)).delete()
|
||||
super().delete()
|
||||
|
||||
@classmethod
|
||||
@@ -139,7 +139,7 @@ class ExternalWebsiteInfoItem(InfoItem):
|
||||
def get_dict(self):
|
||||
"""Convert django model to dict and return it."""
|
||||
d = super().get_dict()
|
||||
d["options"] = {"url": self.url}
|
||||
d["options"] = {'url': self.url}
|
||||
return d
|
||||
|
||||
@classmethod
|
||||
@@ -152,22 +152,23 @@ class ExternalWebsiteInfoItem(InfoItem):
|
||||
def get_list(self):
|
||||
"""Return list containing infoitem data."""
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"url": self.url,
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
'url': self.url,
|
||||
}
|
||||
|
||||
def update_from_dict(self, d):
|
||||
"""Update model based on given dict."""
|
||||
try:
|
||||
expire_date = d.pop("expire_date", None)
|
||||
self.expire_date = datetime.strptime(expire_date, "%Y-%m-%d %H:%M:%S")
|
||||
expire_date = d.pop('expire_date', None)
|
||||
self.expire_date = datetime.strptime(
|
||||
expire_date, "%Y-%m-%d %H:%M:%S")
|
||||
except:
|
||||
pass
|
||||
|
||||
dmap = {
|
||||
"name": "name",
|
||||
"url": "url",
|
||||
'name': 'name',
|
||||
'url': 'url',
|
||||
}
|
||||
for k, v in d.items():
|
||||
try:
|
||||
@@ -240,14 +241,14 @@ class ImageInfoItem(InfoItem):
|
||||
def get_dict(self):
|
||||
"""Convert django model to dict and return it."""
|
||||
d = super().get_dict()
|
||||
d["options"] = {"img": self.img.url}
|
||||
d["options"] = {'img': self.img.url}
|
||||
return d
|
||||
|
||||
|
||||
class VideoInfoItem(InfoItem):
|
||||
"""Class for Video Infoscreen item."""
|
||||
|
||||
display_name = "Video"
|
||||
display_name = ("Video")
|
||||
video = models.FileField(upload_to="infovideos/")
|
||||
|
||||
def get_template_url(self):
|
||||
@@ -262,7 +263,7 @@ class VideoInfoItem(InfoItem):
|
||||
def get_dict(self):
|
||||
"""Convert django model to dict and return it."""
|
||||
d = super().get_dict()
|
||||
d["options"] = {"video": self.video.url}
|
||||
d["options"] = {'video': self.video.url}
|
||||
return d
|
||||
|
||||
|
||||
@@ -284,7 +285,7 @@ class ExternalImageInfoItem(InfoItem):
|
||||
def get_dict(self):
|
||||
"""Convert django model to dict and return it."""
|
||||
d = super().get_dict()
|
||||
d["options"] = {"img": self.url}
|
||||
d["options"] = {'img': self.url}
|
||||
return d
|
||||
|
||||
@classmethod
|
||||
@@ -297,14 +298,15 @@ class ExternalImageInfoItem(InfoItem):
|
||||
def update_from_dict(self, d):
|
||||
"""Update model based on given dict."""
|
||||
try:
|
||||
expire_date = d.pop("expire_date", None)
|
||||
self.expire_date = datetime.strptime(expire_date, "%Y-%m-%d %H:%M:%S")
|
||||
expire_date = d.pop('expire_date', None)
|
||||
self.expire_date = datetime.strptime(
|
||||
expire_date, "%Y-%m-%d %H:%M:%S")
|
||||
except:
|
||||
pass
|
||||
|
||||
dmap = {
|
||||
"name": "name",
|
||||
"url": "url",
|
||||
'name': 'name',
|
||||
'url': 'url',
|
||||
}
|
||||
for k, v in d.items():
|
||||
try:
|
||||
@@ -317,15 +319,12 @@ class ExternalImageInfoItem(InfoItem):
|
||||
class InfoInstance(models.Model):
|
||||
"""Class for Info instance in Infoscreen."""
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
rotation = models.ForeignKey(
|
||||
"Rotation", related_name="instances", on_delete=models.CASCADE
|
||||
)
|
||||
rotation = models.ForeignKey('Rotation', related_name='instances', on_delete=models.CASCADE)
|
||||
duration = models.FloatField(default=15.0) # seconds
|
||||
# generic relation to some kind of InfoItem
|
||||
item_id = models.PositiveIntegerField()
|
||||
item_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||
item = GenericForeignKey("item_type", "item_id")
|
||||
item = GenericForeignKey('item_type', 'item_id')
|
||||
|
||||
@classmethod
|
||||
def create_from_dict(cls, d):
|
||||
@@ -338,27 +337,31 @@ class InfoInstance(models.Model):
|
||||
except:
|
||||
raise RuntimeError("invalid parameters supplied supplied")
|
||||
try:
|
||||
return cls.objects.create(rotation=rotation, item=item, duration=duration)
|
||||
return cls.objects.create(
|
||||
rotation=rotation,
|
||||
item=item,
|
||||
duration=duration
|
||||
)
|
||||
except:
|
||||
raise RuntimeError("error while adding instance to db")
|
||||
|
||||
def get_dict(self):
|
||||
"""Convert django model to dict and return it."""
|
||||
return {
|
||||
"id": self.id,
|
||||
"item": self.item.get_dict(),
|
||||
"duration": self.duration,
|
||||
'id': self.id,
|
||||
'item': self.item.get_dict(),
|
||||
'duration': self.duration,
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
"""Return model name."""
|
||||
return "{}: {} ({}s)".format(self.rotation.name, self.item.name, self.duration)
|
||||
return "{}: {} ({}s)".format(
|
||||
self.rotation.name, self.item.name, self.duration)
|
||||
|
||||
|
||||
class Rotation(models.Model):
|
||||
"""Class for rotation model."""
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = models.CharField(max_length=255)
|
||||
|
||||
def get_dict(self):
|
||||
@@ -367,20 +370,21 @@ class Rotation(models.Model):
|
||||
# to avoid excluding items with no expire_date)
|
||||
now = timezone.now()
|
||||
instances = self.instances.all()
|
||||
filtered = filter(lambda i: (i.item.expire_date or now) >= now, list(instances))
|
||||
filtered = filter(lambda i: (i.item.expire_date or now) >= now,
|
||||
list(instances))
|
||||
instance_list = list(map(lambda i: i.get_dict(), filtered))
|
||||
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"instances": instance_list,
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
'instances': instance_list,
|
||||
}
|
||||
|
||||
def get_list(self):
|
||||
"""Return list containing infoitem data."""
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
@@ -391,7 +395,6 @@ class Rotation(models.Model):
|
||||
class ImageUploadForm(forms.Form):
|
||||
"""Form used to handle imageuploads to infoscreen app."""
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = forms.CharField()
|
||||
image = forms.ImageField()
|
||||
|
||||
@@ -399,6 +402,5 @@ class ImageUploadForm(forms.Form):
|
||||
class UploadFileForm(forms.Form):
|
||||
"""Form used for uploading file."""
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = forms.CharField()
|
||||
video = forms.FileField()
|
||||
|
||||
@@ -3,7 +3,7 @@ body {
|
||||
}
|
||||
|
||||
#header:after {
|
||||
content: " ";
|
||||
content: " ";
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ body {
|
||||
.event {
|
||||
font-size: 100px;
|
||||
font-weight: bold;
|
||||
margin-left: 20px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
.event-col{
|
||||
padding-top:1vh;
|
||||
@@ -21,7 +21,7 @@ body {
|
||||
|
||||
.header-row{
|
||||
margin: 30px;
|
||||
margin-left: 20px;
|
||||
margin-left: 20px;
|
||||
font-size: 130px;
|
||||
padding-bottom:20px;
|
||||
color:#24a05f;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#infocontent {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
z-index: -1; /* Ensure div tag stays behind content; -999 might work, too. */
|
||||
}
|
||||
|
||||
@@ -31,10 +31,10 @@
|
||||
max-height 200px;
|
||||
}
|
||||
|
||||
#sossoimage {
|
||||
#sossoimage {
|
||||
height:300px;
|
||||
position: relative;
|
||||
left: 0px;
|
||||
position: relative;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<link rel="stylesheet" href="/static/infoscreen/css/events.css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Droid+Sans+Mono" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css?family=Droid+Sans+Mono" rel="stylesheet">
|
||||
<div class="container" ng-app="myApp" ng-controller="EventController">
|
||||
<div class="header-row row">
|
||||
<div class="col-sm-6">Tapahtuma</div>
|
||||
|
||||
@@ -56,8 +56,7 @@ app.filter('unsafe', function($sce) {
|
||||
app.controller('ABBController', function($scope, $http){
|
||||
$scope.jobs = [];
|
||||
var min_date = moment().subtract(30,'days').format("YYYY-MM-DD%20HH:mm:ss");
|
||||
// TODO: FIX, we try to get rid of php, not depend on it!
|
||||
var url = "https://old.sahkoinsinoorikilta.fi/api/news.php";
|
||||
var url = "https://sahkoinsinoorikilta.fi/api/news.php";
|
||||
var params = "?type=11&lang=fi&title_search=ABB&min_date="+min_date
|
||||
$http.get(url+params).then(function(response){
|
||||
$scope.jobs = _.filter(response.data, function(job){
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load staticfiles %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
|
||||
+7
-6
@@ -1,7 +1,8 @@
|
||||
{% extends "infoscreen/base.html" %}
|
||||
{% extends "infoscreen:base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block appname %}infoAdmin{% endblock appname %}
|
||||
|
||||
@@ -32,13 +33,13 @@
|
||||
<h1>{% trans "Infoscreen Admin Pane" %}</h1>
|
||||
</div>
|
||||
</div>
|
||||
{% include "infoscreen/nav.html" %}
|
||||
{% include "infoscreen:nav.html" %}
|
||||
<div class="tab-content" id="tabContent">
|
||||
{% include "infoscreen/tabs/slides.html" %}
|
||||
{% include "infoscreen/tabs/rotations.html" %}
|
||||
{% include "infoscreen/tabs/add_remove.html" %}
|
||||
{% include "infoscreen:tabs/slides.html" %}
|
||||
{% include "infoscreen:tabs/rotations.html" %}
|
||||
{% include "infoscreen:tabs/add_remove.html" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include "footer.html" %}
|
||||
{% include "webapp:footer.html" %}
|
||||
{% endblock body %}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
{% extends "infoscreen/base.html" %}
|
||||
{% extends "infoscreen:base.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<div class="col">
|
||||
<div class="rotation-title-row">
|
||||
<h2>{% trans "Rotation" %}: {$ selected_rot.name $}</h2>
|
||||
<a class="btn btn-primary" href="/infoscreen/{$ selected_rot.id $}">{% trans "Preview" %}</a>
|
||||
<a class="btn btn-primary" href="/infoscreen/{$ selected_rot.id $}">{% trans "Preview" %}</a>
|
||||
</div>
|
||||
<div>{% trans "Instances in currently selected rotation" %}:</div>
|
||||
<table class="table table-striped">
|
||||
+1
-1
@@ -35,6 +35,6 @@ class InfoscreenTestCase(TestCase):
|
||||
That would mean that something meaningful has been included
|
||||
in the response.
|
||||
"""
|
||||
resp = self.c.get("/infoscreen/items")
|
||||
resp = self.c.get('/infoscreen/items')
|
||||
content = resp.json()
|
||||
self.assertTrue(len(content) > 0)
|
||||
|
||||
+23
-24
@@ -1,6 +1,6 @@
|
||||
"""File containing infoscreen urls."""
|
||||
|
||||
from django.urls import re_path
|
||||
from django.conf.urls import url
|
||||
from django.conf import settings
|
||||
|
||||
from infoscreen.views import index
|
||||
@@ -27,31 +27,30 @@ from infoscreen.views import createApyItem
|
||||
from infoscreen.views import get_apy_json
|
||||
|
||||
urlpatterns = [
|
||||
re_path(r"^$", default),
|
||||
re_path(r"^admin$", admin),
|
||||
re_path(r"^(?P<idx>\d+)$", index),
|
||||
re_path(r"^items$", info_items),
|
||||
re_path(r"^rotation/(?P<idx>\d+)$", rotation),
|
||||
re_path(r"^rotations$", rotations),
|
||||
re_path(r"^instance$", createInstance),
|
||||
re_path(r"^instance/(?P<idx>\d+)$", deleteInstance),
|
||||
re_path(r"^types$", info_types),
|
||||
re_path(r"^delete_item/(?P<type_id>\d+)/(?P<idx>\d+)$", delete_info_item),
|
||||
re_path(r"^create_external_image$", createExternalImageInfoItem),
|
||||
re_path(r"^create_image$", create_image_item),
|
||||
re_path(r"^create_video$", create_video_item),
|
||||
re_path(r"^create_abbitem$", createABBItem),
|
||||
re_path(r"^create_sossoitem$", createSossoItem),
|
||||
re_path(r"^create_lunchitem$", createLunchItem),
|
||||
re_path(r"^create_eventitem$", createEventItem),
|
||||
re_path(r"^create_apyitem$", createApyItem),
|
||||
re_path(r"^create_websiteitem$", createExternalWebsiteItem),
|
||||
re_path(r"^create_rotation$", create_rotation),
|
||||
re_path(r"^delete_rotation/(?P<id>\d+)$", delete_rotation),
|
||||
re_path(r"^apyjson", get_apy_json),
|
||||
url(r'^$', default),
|
||||
url(r'^admin$', admin),
|
||||
url(r'^(?P<idx>\d+)$', index),
|
||||
url(r'^items$', info_items),
|
||||
url(r'^rotation/(?P<idx>\d+)$', rotation),
|
||||
url(r'^rotations$', rotations),
|
||||
url(r'^instance$', createInstance),
|
||||
url(r'^instance/(?P<idx>\d+)$', deleteInstance),
|
||||
url(r'^types$', info_types),
|
||||
url(r'^delete_item/(?P<type_id>\d+)/(?P<idx>\d+)$', delete_info_item),
|
||||
url(r'^create_external_image$', createExternalImageInfoItem),
|
||||
url(r'^create_image$', create_image_item),
|
||||
url(r'^create_video$', create_video_item),
|
||||
url(r'^create_abbitem$', createABBItem),
|
||||
url(r'^create_sossoitem$', createSossoItem),
|
||||
url(r'^create_lunchitem$', createLunchItem),
|
||||
url(r'^create_eventitem$', createEventItem),
|
||||
url(r'^create_apyitem$', createApyItem),
|
||||
url(r'^create_websiteitem$', createExternalWebsiteItem),
|
||||
url(r'^create_rotation$', create_rotation),
|
||||
url(r'^delete_rotation/(?P<id>\d+)$', delete_rotation),
|
||||
url(r'^apyjson', get_apy_json),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
|
||||
urlpatterns += staticfiles_urlpatterns()
|
||||
|
||||
@@ -16,27 +16,16 @@ import threading
|
||||
import requests
|
||||
|
||||
from infoscreen.models import (
|
||||
Rotation,
|
||||
InfoItem,
|
||||
InfoInstance,
|
||||
ABBInfoItem,
|
||||
ExternalImageInfoItem,
|
||||
ImageInfoItem,
|
||||
SossoInfoItem,
|
||||
LunchItem,
|
||||
EventInfoItem,
|
||||
ExternalWebsiteInfoItem,
|
||||
ImageUploadForm,
|
||||
ApyInfoItem,
|
||||
VideoInfoItem,
|
||||
)
|
||||
Rotation, InfoItem, InfoInstance, ABBInfoItem, ExternalImageInfoItem,
|
||||
ImageInfoItem, SossoInfoItem, LunchItem, EventInfoItem,
|
||||
ExternalWebsiteInfoItem, ImageUploadForm, ApyInfoItem, VideoInfoItem)
|
||||
|
||||
|
||||
@login_required(login_url="/admin/login")
|
||||
@permission_required("infoscreen.change_infoinstance", raise_exception=True)
|
||||
@login_required(login_url='/admin/login')
|
||||
@permission_required('infoscreen.change_infoinstance', raise_exception=True)
|
||||
def admin(request, *args, **kwargs):
|
||||
"""Render infoscreen admin page."""
|
||||
return render(request, "infoscreen/infoscreen_admin.html", {})
|
||||
return render(request, 'infoscreen:infoscreen_admin.html', {})
|
||||
|
||||
|
||||
def create_item_generator(model):
|
||||
@@ -44,23 +33,20 @@ def create_item_generator(model):
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@require_http_methods(["POST"])
|
||||
@login_required(login_url="/admin/login")
|
||||
@permission_required("infoscreen.add_infoinstance", raise_exception=True)
|
||||
@login_required(login_url='/admin/login')
|
||||
@permission_required('infoscreen.add_infoinstance', raise_exception=True)
|
||||
def create_item(request, *args, **kwargs):
|
||||
try:
|
||||
data = json.loads(request.body.decode("utf-8"))
|
||||
except json.JSONDecodeError:
|
||||
return HttpResponseBadRequest(
|
||||
'{"status":"failure","error":"invalid json supplied"}'
|
||||
)
|
||||
'{"status":"failure","error":"invalid json supplied"}')
|
||||
try:
|
||||
model.create_from_dict(data)
|
||||
return HttpResponse('{"status":"success"}')
|
||||
except RuntimeError as e:
|
||||
return HttpResponseBadRequest(
|
||||
json.dumps({"status": "failure", "error": str(e)})
|
||||
)
|
||||
|
||||
json.dumps({"status": "failure", "error": str(e)}))
|
||||
return create_item
|
||||
|
||||
|
||||
@@ -69,8 +55,8 @@ def delete_item_generator(model):
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@require_http_methods(["DELETE"])
|
||||
@login_required(login_url="/admin/login")
|
||||
@permission_required("infoscreen.delete_infoinstance", raise_exception=True)
|
||||
@login_required(login_url='/admin/login')
|
||||
@permission_required('infoscreen.delete_infoinstance', raise_exception=True)
|
||||
def delete_item(request, *args, **kwargs):
|
||||
idx = kwargs.pop("idx", 0)
|
||||
try:
|
||||
@@ -86,14 +72,13 @@ def delete_item_generator(model):
|
||||
resp = HttpResponse('{"error" : "could not delete item"}')
|
||||
resp.status_code = 500
|
||||
return resp
|
||||
|
||||
return delete_item
|
||||
|
||||
|
||||
# due to model structure this is little complicated
|
||||
@ensure_csrf_cookie
|
||||
@login_required(login_url="/admin/login")
|
||||
@permission_required("infoscreen.delete_infoinstance", raise_exception=True)
|
||||
@login_required(login_url='/admin/login')
|
||||
@permission_required('infoscreen.delete_infoinstance', raise_exception=True)
|
||||
@require_http_methods(["DELETE"])
|
||||
def delete_info_item(request, *args, **kwargs):
|
||||
"""Delete info item."""
|
||||
@@ -117,44 +102,42 @@ def delete_info_item(request, *args, **kwargs):
|
||||
|
||||
@require_http_methods(["POST"])
|
||||
@ensure_csrf_cookie
|
||||
@login_required(login_url="/admin/login")
|
||||
@permission_required("infoscreen.add_infoinstance", raise_exception=True)
|
||||
@login_required(login_url='/admin/login')
|
||||
@permission_required('infoscreen.add_infoinstance', raise_exception=True)
|
||||
def create_image_item(request, *args, **kwargs):
|
||||
"""Create image Infoscreen item."""
|
||||
form = ImageUploadForm(request.POST, request.FILES)
|
||||
if not form.is_valid():
|
||||
return HttpResponseBadRequest(
|
||||
'{"status": "failure",' '"error": "invalid data supplied"}'
|
||||
)
|
||||
return HttpResponseBadRequest('{"status": "failure",'
|
||||
'"error": "invalid data supplied"}')
|
||||
|
||||
img = form.cleaned_data["image"]
|
||||
name = form.cleaned_data["name"]
|
||||
img = form.cleaned_data['image']
|
||||
name = form.cleaned_data['name']
|
||||
ImageInfoItem.objects.create(img=img, name=name)
|
||||
return HttpResponse('{"status":"success"}')
|
||||
|
||||
|
||||
@require_http_methods(["POST"])
|
||||
@ensure_csrf_cookie
|
||||
@login_required(login_url="/admin/login")
|
||||
@permission_required("infoscreen.add_infoinstance", raise_exception=True)
|
||||
@login_required(login_url='/admin/login')
|
||||
@permission_required('infoscreen.add_infoinstance', raise_exception=True)
|
||||
def create_video_item(request, *args, **kwargs):
|
||||
"""Create video Infoscreen item."""
|
||||
form = UploadFileForm(request.POST, request.FILES)
|
||||
if not form.is_valid():
|
||||
return HttpResponseBadRequest(
|
||||
'{"status": "failure",' '"error": "invalid data supplied"}'
|
||||
)
|
||||
return HttpResponseBadRequest('{"status": "failure",'
|
||||
'"error": "invalid data supplied"}')
|
||||
|
||||
video = form.cleaned_data["video"]
|
||||
name = form.cleaned_data["name"]
|
||||
video = form.cleaned_data['video']
|
||||
name = form.cleaned_data['name']
|
||||
VideoInfoItem.objects.create(video=video, name=name)
|
||||
return HttpResponse('{"status": "success"}')
|
||||
|
||||
|
||||
@require_http_methods(["POST"])
|
||||
@ensure_csrf_cookie
|
||||
@login_required(login_url="/admin/login")
|
||||
@permission_required("infoscreen.add_rotation", raise_exception=True)
|
||||
@login_required(login_url='/admin/login')
|
||||
@permission_required('infoscreen.add_rotation', raise_exception=True)
|
||||
def create_rotation(request, *args, **kwargs):
|
||||
"""Create rotation."""
|
||||
try:
|
||||
@@ -167,15 +150,16 @@ def create_rotation(request, *args, **kwargs):
|
||||
Rotation.objects.create(name=name)
|
||||
resp = HttpResponse(status=200)
|
||||
except DatabaseError:
|
||||
resp = HttpResponse('{"error" : "could not create rotation!"}', status=400)
|
||||
resp = HttpResponse(
|
||||
'{"error" : "could not create rotation!"}', status=400)
|
||||
|
||||
return resp
|
||||
|
||||
|
||||
@require_http_methods(["DELETE"])
|
||||
@ensure_csrf_cookie
|
||||
@login_required(login_url="/admin/login")
|
||||
@permission_required("infoscreen.delete_rotation", raise_exception=True)
|
||||
@login_required(login_url='/admin/login')
|
||||
@permission_required('infoscreen.delete_rotation', raise_exception=True)
|
||||
def delete_rotation(request, *args, **kwargs):
|
||||
"""Delete rotation."""
|
||||
id = kwargs.pop("id", 0)
|
||||
@@ -185,7 +169,8 @@ def delete_rotation(request, *args, **kwargs):
|
||||
Rotation.objects.filter(id=id).delete()
|
||||
resp = HttpResponse(status=200)
|
||||
except DatabaseError:
|
||||
resp = HttpResponse('{"error" : "could not delete rotation!"}', status=400)
|
||||
resp = HttpResponse(
|
||||
'{"error" : "could not delete rotation!"}', status=400)
|
||||
|
||||
return resp
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import requests
|
||||
@require_http_methods(["GET"])
|
||||
def index(request, idx, *args, **kwargs):
|
||||
"""Render infoscreen index page."""
|
||||
return render(request, "infoscreen/infoscreen_index.html", {"rotation": idx})
|
||||
return render(request, 'infoscreen_index.html', {'rotation': idx})
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
@@ -32,8 +32,7 @@ def default(request, *args, **kwargs):
|
||||
def get_apy_json(request):
|
||||
"""Render APY diilikone page."""
|
||||
return HttpResponse(
|
||||
requests.get("https://api-diilikone.apy.fi/deals/top-groups").text
|
||||
)
|
||||
requests.get("https://api-diilikone.apy.fi/deals/top-groups").text)
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
@@ -62,12 +61,10 @@ def info_types(request, *args, **kwargs):
|
||||
types = []
|
||||
classes = InfoItem.get_subclasses()
|
||||
for c in classes:
|
||||
types.append(
|
||||
{
|
||||
"name": c.display_name,
|
||||
"create_template_url": c.get_create_template_url(),
|
||||
}
|
||||
)
|
||||
types.append({
|
||||
"name": c.display_name,
|
||||
"create_template_url": c.get_create_template_url(),
|
||||
})
|
||||
return HttpResponse(json.dumps(types))
|
||||
|
||||
|
||||
|
||||
+2
-1
@@ -1,9 +1,10 @@
|
||||
from django.contrib import admin
|
||||
from modeltranslation.admin import TranslationAdmin
|
||||
|
||||
from kaehmy.models import Application, Comment, CustomRole, PresetRole
|
||||
from kaehmy.models import Application, Comment, CustomRole, PresetRole, TelegramChannel
|
||||
|
||||
admin.site.register(Application)
|
||||
admin.site.register(Comment)
|
||||
admin.site.register(CustomRole)
|
||||
admin.site.register(PresetRole, TranslationAdmin)
|
||||
admin.site.register(TelegramChannel)
|
||||
|
||||
+1
-1
@@ -2,4 +2,4 @@ from django.apps import AppConfig
|
||||
|
||||
|
||||
class KaehmyConfig(AppConfig):
|
||||
name = "kaehmy"
|
||||
name = 'kaehmy'
|
||||
|
||||
+30
-55
@@ -1,21 +1,17 @@
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from kaehmy.models import PresetRole, CustomRole, Application, Comment, BaseRole
|
||||
from kaehmy.models import PresetRole, CustomRole, Application, Comment, KaehmyBaseRole
|
||||
|
||||
|
||||
class CheckboxSelectMultiple(forms.widgets.CheckboxSelectMultiple):
|
||||
option_template_name = "checkbox_option.html"
|
||||
option_template_name = 'checkbox_option.html'
|
||||
|
||||
def create_option(
|
||||
self, name, formIterator, label, selected, index, subindex=None, attrs=None
|
||||
):
|
||||
dic = super(CheckboxSelectMultiple, self).create_option(
|
||||
name, formIterator, label, selected, index, subindex, attrs
|
||||
)
|
||||
description = PresetRole.objects.get(id=formIterator.value).description
|
||||
dic["description"] = description
|
||||
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):
|
||||
@@ -29,46 +25,30 @@ class ApplicationForm(forms.ModelForm):
|
||||
"""Meta for class Application."""
|
||||
|
||||
model = Application
|
||||
fields = [
|
||||
"name",
|
||||
"email",
|
||||
"phone_number",
|
||||
"year",
|
||||
"preset_roles",
|
||||
"custom_roles",
|
||||
"custom_role_name",
|
||||
"custom_role_is_board",
|
||||
"text",
|
||||
]
|
||||
fields = ['name', 'email', 'phone_number', 'year',
|
||||
'preset_roles', 'custom_roles', 'custom_role_name',
|
||||
'custom_role_is_board', 'text']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ApplicationForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.fields["email"].label = _("Email (not public)")
|
||||
self.fields["phone_number"].label = _("Phone number (not public)")
|
||||
self.fields["email"].label = _('Email (not public)')
|
||||
self.fields["phone_number"].label = _('Phone number (not public)')
|
||||
|
||||
custom_roles_exist = CustomRole.objects.all().exists()
|
||||
self.fields["custom_roles"].widget = (
|
||||
forms.widgets.CheckboxSelectMultiple()
|
||||
if custom_roles_exist
|
||||
else forms.HiddenInput()
|
||||
)
|
||||
self.fields["custom_roles"].widget = forms.widgets.CheckboxSelectMultiple() if custom_roles_exist else forms.HiddenInput()
|
||||
self.fields["custom_roles"].help_text = ""
|
||||
self.fields["custom_roles"].label = _("Custom roles")
|
||||
self.fields["custom_roles"].label = _('Custom roles')
|
||||
self.fields["custom_roles"].queryset = CustomRole.objects.all()
|
||||
|
||||
for cat_id, category in BaseRole.CATEGORIES:
|
||||
key = "preset_roles_{}".format(cat_id)
|
||||
qset = PresetRole.objects.filter(category=cat_id).order_by(
|
||||
"category", "-is_board"
|
||||
)
|
||||
for cat_id, category in KaehmyBaseRole.CATEGORIES:
|
||||
key = 'preset_roles_{}'.format(cat_id)
|
||||
qset = PresetRole.objects.filter(category=cat_id).order_by('category', '-is_board')
|
||||
self.fields[key] = forms.ModelMultipleChoiceField(qset)
|
||||
self.fields[key].widget = CheckboxSelectMultiple(
|
||||
attrs={
|
||||
"title": _("Preset roles"),
|
||||
"name": "preset_roles",
|
||||
}
|
||||
)
|
||||
self.fields[key].widget = CheckboxSelectMultiple(attrs={
|
||||
'title': _('Preset roles'),
|
||||
'name': 'preset_roles',
|
||||
})
|
||||
self.fields[key].help_text = ""
|
||||
self.fields[key].queryset = qset
|
||||
self.fields[key].label = _(category)
|
||||
@@ -77,38 +57,33 @@ class ApplicationForm(forms.ModelForm):
|
||||
def clean(self):
|
||||
cleaned_data = super(ApplicationForm, self).clean()
|
||||
for key in cleaned_data.keys():
|
||||
if "preset_roles_" in key:
|
||||
cleaned_data["preset_roles"] = (
|
||||
cleaned_data["preset_roles"] | cleaned_data[key]
|
||||
)
|
||||
if 'preset_roles_' in key:
|
||||
cleaned_data['preset_roles'] = cleaned_data['preset_roles'] | cleaned_data[key]
|
||||
|
||||
return cleaned_data
|
||||
|
||||
def clean_phone_number(self):
|
||||
"""Clean phone number field."""
|
||||
number = self.cleaned_data.get("phone_number")
|
||||
number = self.cleaned_data.get('phone_number')
|
||||
if number.isdigit():
|
||||
return number
|
||||
else:
|
||||
raise ValidationError(_("Invalid phone number"))
|
||||
raise ValidationError(_('Invalid phone number'))
|
||||
|
||||
def clean_custom_role_name(self):
|
||||
"""Check that no other custom role with same name exists."""
|
||||
custom_name = self.cleaned_data.get("custom_role_name")
|
||||
custom_name = self.cleaned_data.get('custom_role_name')
|
||||
if not CustomRole.objects.filter(name=custom_name).exists():
|
||||
return custom_name
|
||||
else:
|
||||
raise ValidationError(_("Custom role with the same name already exists."))
|
||||
raise ValidationError(_('Custom role with the same name already exists.'))
|
||||
|
||||
def non_role_fields(self):
|
||||
return [
|
||||
self.fields[k]
|
||||
for k in self.fields.keys()
|
||||
if k not in ["preset_roles", "custom_roles"]
|
||||
]
|
||||
return [self.fields[k] for k in self.fields.keys() if k not in ["preset_roles", "custom_roles"]]
|
||||
|
||||
|
||||
class CommentForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Comment
|
||||
fields = ["name", "email", "message", "parent"]
|
||||
fields = ['name', 'email', 'message', 'parent']
|
||||
|
||||
@@ -12,188 +12,82 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("webapp", "0037_auto_20180125_2131"),
|
||||
('webapp', '0037_auto_20180125_2131'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="CommentParent",
|
||||
name='CommentParent',
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"name",
|
||||
models.CharField(default="", max_length=255, verbose_name="Name"),
|
||||
),
|
||||
(
|
||||
"email",
|
||||
models.EmailField(default="", max_length=254, verbose_name="Email"),
|
||||
),
|
||||
(
|
||||
"timestamp",
|
||||
models.DateTimeField(
|
||||
default=django.utils.timezone.now, verbose_name="Timestamp"
|
||||
),
|
||||
),
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(default='', max_length=255, verbose_name='Name')),
|
||||
('email', models.EmailField(default='', max_length=254, verbose_name='Email')),
|
||||
('timestamp', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Timestamp')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="CustomRole",
|
||||
name='CustomRole',
|
||||
fields=[
|
||||
(
|
||||
"baserole_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="webapp.BaseRole",
|
||||
),
|
||||
),
|
||||
('baserole_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='webapp.BaseRole')),
|
||||
],
|
||||
options={
|
||||
"verbose_name_plural": "Custom kaehmy roles",
|
||||
"verbose_name": "Custom kaehmy role",
|
||||
'verbose_name_plural': 'Custom kaehmy roles',
|
||||
'verbose_name': 'Custom kaehmy role',
|
||||
},
|
||||
bases=("webapp.baserole",),
|
||||
bases=('webapp.baserole',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="PresetRole",
|
||||
name='PresetRole',
|
||||
fields=[
|
||||
(
|
||||
"presetrole_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="webapp.PresetRole",
|
||||
),
|
||||
),
|
||||
('presetrole_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='webapp.PresetRole')),
|
||||
],
|
||||
options={
|
||||
"verbose_name_plural": "Preset kaehmy roles",
|
||||
"verbose_name": "Preset kaehmy role",
|
||||
'verbose_name_plural': 'Preset kaehmy roles',
|
||||
'verbose_name': 'Preset kaehmy role',
|
||||
},
|
||||
bases=("webapp.presetrole",),
|
||||
bases=('webapp.presetrole',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="TelegramChannel",
|
||||
name='TelegramChannel',
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=255)),
|
||||
("channel_id", models.CharField(max_length=255, unique=True)),
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('channel_id', models.CharField(max_length=255, unique=True)),
|
||||
],
|
||||
options={
|
||||
"verbose_name_plural": "Telegram channels",
|
||||
"verbose_name": "Telegram channel",
|
||||
'verbose_name_plural': 'Telegram channels',
|
||||
'verbose_name': 'Telegram channel',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Application",
|
||||
name='Application',
|
||||
fields=[
|
||||
(
|
||||
"commentparent_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="kaehmy.CommentParent",
|
||||
),
|
||||
),
|
||||
(
|
||||
"phone_number",
|
||||
models.CharField(
|
||||
default="", max_length=10, verbose_name="Phone number"
|
||||
),
|
||||
),
|
||||
(
|
||||
"year",
|
||||
models.IntegerField(
|
||||
choices=[(1, "1"), (2, "2"), (3, "3"), (4, "4"), (5, "N")],
|
||||
verbose_name="Year",
|
||||
),
|
||||
),
|
||||
(
|
||||
"text",
|
||||
models.TextField(default="", max_length=300, verbose_name="Text"),
|
||||
),
|
||||
(
|
||||
"custom_role_name",
|
||||
models.CharField(
|
||||
blank=True, max_length=255, verbose_name="Custom role name"
|
||||
),
|
||||
),
|
||||
(
|
||||
"custom_role_is_board",
|
||||
models.BooleanField(verbose_name="Board member"),
|
||||
),
|
||||
(
|
||||
"custom_roles",
|
||||
models.ManyToManyField(
|
||||
blank=True, related_name="forms", to="kaehmy.CustomRole"
|
||||
),
|
||||
),
|
||||
(
|
||||
"preset_roles",
|
||||
models.ManyToManyField(
|
||||
blank=True, related_name="forms", to="kaehmy.PresetRole"
|
||||
),
|
||||
),
|
||||
('commentparent_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='kaehmy.CommentParent')),
|
||||
('phone_number', models.CharField(default='', max_length=10, verbose_name='Phone number')),
|
||||
('year', models.IntegerField(choices=[(1, '1'), (2, '2'), (3, '3'), (4, '4'), (5, 'N')], verbose_name='Year')),
|
||||
('text', models.TextField(default='', max_length=300, verbose_name='Text')),
|
||||
('custom_role_name', models.CharField(blank=True, max_length=255, verbose_name='Custom role name')),
|
||||
('custom_role_is_board', models.BooleanField(verbose_name='Board member')),
|
||||
('custom_roles', models.ManyToManyField(blank=True, related_name='forms', to='kaehmy.CustomRole')),
|
||||
('preset_roles', models.ManyToManyField(blank=True, related_name='forms', to='kaehmy.PresetRole')),
|
||||
],
|
||||
options={
|
||||
"verbose_name_plural": "Kaehmylomakkeet",
|
||||
"verbose_name": "Kaehmylomake",
|
||||
'verbose_name_plural': 'Kaehmylomakkeet',
|
||||
'verbose_name': 'Kaehmylomake',
|
||||
},
|
||||
bases=("kaehmy.commentparent",),
|
||||
bases=('kaehmy.commentparent',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Comment",
|
||||
name='Comment',
|
||||
fields=[
|
||||
(
|
||||
"commentparent_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="kaehmy.CommentParent",
|
||||
),
|
||||
),
|
||||
("message", models.TextField(verbose_name="Message")),
|
||||
(
|
||||
"parent",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="messages",
|
||||
to="kaehmy.CommentParent",
|
||||
),
|
||||
),
|
||||
('commentparent_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='kaehmy.CommentParent')),
|
||||
('message', models.TextField(verbose_name='Message')),
|
||||
('parent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='kaehmy.CommentParent')),
|
||||
],
|
||||
options={
|
||||
"verbose_name_plural": "Kaehmykommentit",
|
||||
"verbose_name": "Kaehmykommentti",
|
||||
'verbose_name_plural': 'Kaehmykommentit',
|
||||
'verbose_name': 'Kaehmykommentti',
|
||||
},
|
||||
bases=("kaehmy.commentparent",),
|
||||
bases=('kaehmy.commentparent',),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -7,59 +7,26 @@ import django.db.models.deletion
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("webapp", "0047_auto_20180710_2110"),
|
||||
("kaehmy", "0001_initial"),
|
||||
('webapp', '0047_auto_20180710_2110'),
|
||||
('kaehmy', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="KaehmyBaseRole",
|
||||
name='KaehmyBaseRole',
|
||||
fields=[
|
||||
(
|
||||
"baserole_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="webapp.BaseRole",
|
||||
),
|
||||
),
|
||||
(
|
||||
"category",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("corporate", "Corporate affairs"),
|
||||
("freshman", "Freshmen"),
|
||||
("international", "International"),
|
||||
("external", "External affairs"),
|
||||
("media", "Media"),
|
||||
("tech", "Technology"),
|
||||
("wellbeing", "Wellbeing"),
|
||||
("elepaja", "Elepaja"),
|
||||
("ceremonies", "Ceremonies"),
|
||||
("culture", "Culture"),
|
||||
("studies", "Studies"),
|
||||
("sosso", "Sössö magazine"),
|
||||
("alumni", "Alumni relations"),
|
||||
("others", "Others"),
|
||||
],
|
||||
default="others",
|
||||
max_length=255,
|
||||
verbose_name="Category",
|
||||
),
|
||||
),
|
||||
('baserole_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='webapp.BaseRole')),
|
||||
('category', models.CharField(choices=[('corporate', 'Corporate affairs'), ('freshman', 'Freshmen'), ('international', 'International'), ('external', 'External affairs'), ('media', 'Media'), ('tech', 'Technology'), ('wellbeing', 'Wellbeing'), ('elepaja', 'Elepaja'), ('ceremonies', 'Ceremonies'), ('culture', 'Culture'), ('studies', 'Studies'), ('sosso', 'Sössö magazine'), ('alumni', 'Alumni relations'), ('others', 'Others')], default='others', max_length=255, verbose_name='Category')),
|
||||
],
|
||||
bases=("webapp.baserole",),
|
||||
bases=('webapp.baserole',),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name="Application",
|
||||
name='Application',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name="customrole",
|
||||
name='customrole',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name="presetrole",
|
||||
name='presetrole',
|
||||
),
|
||||
]
|
||||
|
||||
@@ -7,113 +7,57 @@ import django.db.models.deletion
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("kaehmy", "0002_auto_20180902_1929"),
|
||||
('kaehmy', '0002_auto_20180902_1929'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Application",
|
||||
name='Application',
|
||||
fields=[
|
||||
(
|
||||
"commentparent_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="kaehmy.CommentParent",
|
||||
),
|
||||
),
|
||||
(
|
||||
"phone_number",
|
||||
models.CharField(
|
||||
default="", max_length=10, verbose_name="Phone number"
|
||||
),
|
||||
),
|
||||
(
|
||||
"year",
|
||||
models.IntegerField(
|
||||
choices=[(1, "1"), (2, "2"), (3, "3"), (4, "4"), (5, "N")],
|
||||
verbose_name="Year",
|
||||
),
|
||||
),
|
||||
(
|
||||
"text",
|
||||
models.TextField(default="", max_length=300, verbose_name="Text"),
|
||||
),
|
||||
(
|
||||
"custom_role_name",
|
||||
models.CharField(
|
||||
blank=True, max_length=255, verbose_name="Custom role name"
|
||||
),
|
||||
),
|
||||
(
|
||||
"custom_role_is_board",
|
||||
models.BooleanField(verbose_name="Board member"),
|
||||
),
|
||||
('commentparent_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='kaehmy.CommentParent')),
|
||||
('phone_number', models.CharField(default='', max_length=10, verbose_name='Phone number')),
|
||||
('year', models.IntegerField(choices=[(1, '1'), (2, '2'), (3, '3'), (4, '4'), (5, 'N')], verbose_name='Year')),
|
||||
('text', models.TextField(default='', max_length=300, verbose_name='Text')),
|
||||
('custom_role_name', models.CharField(blank=True, max_length=255, verbose_name='Custom role name')),
|
||||
('custom_role_is_board', models.BooleanField(verbose_name='Board member')),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Kaehmylomake",
|
||||
"verbose_name_plural": "Kaehmylomakkeet",
|
||||
'verbose_name': 'Kaehmylomake',
|
||||
'verbose_name_plural': 'Kaehmylomakkeet',
|
||||
},
|
||||
bases=("kaehmy.commentparent",),
|
||||
bases=('kaehmy.commentparent',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="CustomRole",
|
||||
name='CustomRole',
|
||||
fields=[
|
||||
(
|
||||
"kaehmybaserole_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="kaehmy.KaehmyBaseRole",
|
||||
),
|
||||
),
|
||||
('kaehmybaserole_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='kaehmy.KaehmyBaseRole')),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Custom kaehmy role",
|
||||
"verbose_name_plural": "Custom kaehmy roles",
|
||||
'verbose_name': 'Custom kaehmy role',
|
||||
'verbose_name_plural': 'Custom kaehmy roles',
|
||||
},
|
||||
bases=("kaehmy.kaehmybaserole",),
|
||||
bases=('kaehmy.kaehmybaserole',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="PresetRole",
|
||||
name='PresetRole',
|
||||
fields=[
|
||||
(
|
||||
"kaehmybaserole_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="kaehmy.KaehmyBaseRole",
|
||||
),
|
||||
),
|
||||
("description", models.TextField(verbose_name="Description")),
|
||||
('kaehmybaserole_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='kaehmy.KaehmyBaseRole')),
|
||||
('description', models.TextField(verbose_name='Description')),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Preset kaehmy role",
|
||||
"verbose_name_plural": "Preset kaehmy roles",
|
||||
'verbose_name': 'Preset kaehmy role',
|
||||
'verbose_name_plural': 'Preset kaehmy roles',
|
||||
},
|
||||
bases=("kaehmy.kaehmybaserole",),
|
||||
bases=('kaehmy.kaehmybaserole',),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="application",
|
||||
name="custom_roles",
|
||||
field=models.ManyToManyField(
|
||||
blank=True, related_name="forms", to="kaehmy.CustomRole"
|
||||
),
|
||||
model_name='application',
|
||||
name='custom_roles',
|
||||
field=models.ManyToManyField(blank=True, related_name='forms', to='kaehmy.CustomRole'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="application",
|
||||
name="preset_roles",
|
||||
field=models.ManyToManyField(
|
||||
blank=True, related_name="forms", to="kaehmy.PresetRole"
|
||||
),
|
||||
model_name='application',
|
||||
name='preset_roles',
|
||||
field=models.ManyToManyField(blank=True, related_name='forms', to='kaehmy.PresetRole'),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -6,32 +6,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("kaehmy", "0003_auto_20180902_1943"),
|
||||
('kaehmy', '0003_auto_20180902_1943'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="kaehmybaserole",
|
||||
name="category",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("corporate", "Corporate affairs"),
|
||||
("freshman", "Freshmen"),
|
||||
("international", "International"),
|
||||
("external", "External affairs"),
|
||||
("media", "Media"),
|
||||
("tech", "Technology"),
|
||||
("wellbeing", "Wellbeing"),
|
||||
("elepaja", "Elepaja"),
|
||||
("ceremonies", "Ceremonies"),
|
||||
("studies", "Studies"),
|
||||
("sosso", "Sössö magazine"),
|
||||
("alumni", "Alumni relations"),
|
||||
("others", "Others"),
|
||||
],
|
||||
default="others",
|
||||
max_length=255,
|
||||
verbose_name="Category",
|
||||
),
|
||||
model_name='kaehmybaserole',
|
||||
name='category',
|
||||
field=models.CharField(choices=[('corporate', 'Corporate affairs'), ('freshman', 'Freshmen'), ('international', 'International'), ('external', 'External affairs'), ('media', 'Media'), ('tech', 'Technology'), ('wellbeing', 'Wellbeing'), ('elepaja', 'Elepaja'), ('ceremonies', 'Ceremonies'), ('studies', 'Studies'), ('sosso', 'Sössö magazine'), ('alumni', 'Alumni relations'), ('others', 'Others')], default='others', max_length=255, verbose_name='Category'),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("kaehmy", "0004_auto_20181018_2121"),
|
||||
('kaehmy', '0004_auto_20181018_2121'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="application",
|
||||
name="custom_role_is_board",
|
||||
field=models.BooleanField(blank=True, verbose_name="Board member"),
|
||||
model_name='application',
|
||||
name='custom_role_is_board',
|
||||
field=models.BooleanField(blank=True, verbose_name='Board member'),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
# Generated by Django 2.2.26 on 2022-01-12 20:38
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("kaehmy", "0005_auto_20190312_1458"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name="TelegramChannel",
|
||||
),
|
||||
]
|
||||
@@ -1,18 +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 = [
|
||||
("kaehmy", "0006_delete_telegramchannel"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="commentparent",
|
||||
name="id",
|
||||
field=models.AutoField(primary_key=True, serialize=False),
|
||||
),
|
||||
]
|
||||
@@ -1,44 +0,0 @@
|
||||
# Generated by Django 3.2.14 on 2022-08-03 20:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("kaehmy", "0007_alter_commentparent_id"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="BaseRole",
|
||||
fields=[
|
||||
("id", models.AutoField(primary_key=True, serialize=False)),
|
||||
("name", models.CharField(max_length=255, verbose_name="Name")),
|
||||
("is_board", models.BooleanField(verbose_name="Board member")),
|
||||
(
|
||||
"category",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("corporate", "Corporate affairs"),
|
||||
("freshman", "Freshmen"),
|
||||
("international", "International"),
|
||||
("external", "External affairs"),
|
||||
("media", "Media"),
|
||||
("tech", "Technology"),
|
||||
("wellbeing", "Wellbeing"),
|
||||
("elepaja", "Elepaja"),
|
||||
("ceremonies", "Ceremonies"),
|
||||
("studies", "Studies"),
|
||||
("sosso", "Sössö magazine"),
|
||||
("alumni", "Alumni relations"),
|
||||
("others", "Others"),
|
||||
],
|
||||
default="others",
|
||||
max_length=255,
|
||||
verbose_name="Category",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -1,29 +0,0 @@
|
||||
# Generated by Django 2.2.28 on 2022-07-26 17:15
|
||||
|
||||
from unicodedata import category
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def copyBaseRolesToNewTable(apps, schema_editor):
|
||||
Old = apps.get_model("kaehmy", "KaehmyBaseRole")
|
||||
New = apps.get_model("kaehmy", "BaseRole")
|
||||
for bases in Old.objects.all():
|
||||
New.objects.create(
|
||||
id=bases.id,
|
||||
name=bases.name,
|
||||
is_board=bases.is_board,
|
||||
category=bases.category,
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("kaehmy", "0008_baserole"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
copyBaseRolesToNewTable, reverse_code=migrations.RunPython.noop
|
||||
),
|
||||
]
|
||||
@@ -1,51 +0,0 @@
|
||||
# Generated by Django 2.2.28 on 2022-07-26 17:33
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
from sikweb.custom_operations import AlterModelBases
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("kaehmy", "0009_auto_20220726_2015"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
AlterModelBases("customrole", (models.Model,)),
|
||||
AlterModelBases("presetrole", (models.Model,)),
|
||||
migrations.AlterField(
|
||||
model_name="customrole",
|
||||
name="kaehmybaserole_ptr",
|
||||
field=models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="BaseRole",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="presetrole",
|
||||
name="kaehmybaserole_ptr",
|
||||
field=models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="BaseRole",
|
||||
),
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="customrole",
|
||||
old_name="kaehmybaserole_ptr",
|
||||
new_name="baserole_ptr",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="presetrole",
|
||||
old_name="kaehmybaserole_ptr",
|
||||
new_name="baserole_ptr",
|
||||
),
|
||||
]
|
||||
@@ -1,16 +0,0 @@
|
||||
# Generated by Django 3.2.14 on 2022-08-03 20:19
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("kaehmy", "0010_auto_20220726_2033"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name="KaehmyBaseRole",
|
||||
),
|
||||
]
|
||||
+78
-82
@@ -1,71 +1,64 @@
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from datetime import timedelta
|
||||
from webapp.utils import month_from_now
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from auditlog.registry import auditlog
|
||||
from phonenumber_field.modelfields import PhoneNumberField
|
||||
|
||||
VERBOSE_NAME = _("Kaehmy")
|
||||
import logging
|
||||
|
||||
import webapp.models
|
||||
|
||||
VERBOSE_NAME = _('Kaehmy')
|
||||
|
||||
|
||||
class BaseRole(models.Model):
|
||||
"""Base model for occupations/roles."""
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = models.CharField(_("Name"), max_length=255)
|
||||
is_board = models.BooleanField(_("Board member"))
|
||||
class KaehmyBaseRole(webapp.models.BaseRole):
|
||||
|
||||
CATEGORIES = (
|
||||
("board", _("Board")),
|
||||
("corporate", _("Corporate affairs")),
|
||||
("freshman", _("Freshmen")),
|
||||
("international", _("International")),
|
||||
("siwa", _("SIK's free time")),
|
||||
("media", _("Media")),
|
||||
("tech", _("Technology")),
|
||||
("wellbeing", _("Wellbeing")),
|
||||
("sikpaja", _("Sik-paja")),
|
||||
("ceremonies", _("Ceremonies")),
|
||||
("studies", _("Studies")),
|
||||
("sosso", _("Sössö magazine")),
|
||||
("pota", _("PoTa")),
|
||||
("alumni", _("Alumni relations")),
|
||||
("n", _("N")),
|
||||
("others", _("Others")),
|
||||
('corporate', _('Corporate affairs')),
|
||||
('freshman', _('Freshmen')),
|
||||
('international', _('International')),
|
||||
('external', _('External affairs')),
|
||||
('media', _('Media')),
|
||||
('tech', _('Technology')),
|
||||
('wellbeing', _('Wellbeing')),
|
||||
('elepaja', _('Elepaja')),
|
||||
('ceremonies', _('Ceremonies')),
|
||||
('studies', _('Studies')),
|
||||
('sosso', _('Sössö magazine')),
|
||||
('alumni', _('Alumni relations')),
|
||||
('others', _('Others')),
|
||||
)
|
||||
category = models.CharField(
|
||||
_("Category"), choices=CATEGORIES, default="others", max_length=255
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
n = self.name.capitalize()
|
||||
return "{} ({})".format(n, _("board member")) if self.is_board else n
|
||||
category = models.CharField(_('Category'), choices=CATEGORIES, default='others', max_length=255)
|
||||
|
||||
|
||||
class PresetRole(BaseRole):
|
||||
class PresetRole(KaehmyBaseRole):
|
||||
"""Model for kaehmy role."""
|
||||
|
||||
description = models.TextField(_("Description"))
|
||||
description = models.TextField(_('Description'))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Preset kaehmy role")
|
||||
verbose_name_plural = _("Preset kaehmy roles")
|
||||
verbose_name = _('Preset kaehmy role')
|
||||
verbose_name_plural = _('Preset kaehmy roles')
|
||||
|
||||
|
||||
class CustomRole(BaseRole):
|
||||
class CustomRole(KaehmyBaseRole):
|
||||
"""Model representing a user-specified custom occupation."""
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Custom kaehmy role")
|
||||
verbose_name_plural = _("Custom kaehmy roles")
|
||||
verbose_name = _('Custom kaehmy role')
|
||||
verbose_name_plural = _('Custom kaehmy roles')
|
||||
|
||||
|
||||
class CommentParent(models.Model):
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = models.CharField(_("Name"), max_length=255, default="")
|
||||
email = models.EmailField(_("Email"), default="")
|
||||
timestamp = models.DateTimeField(_("Timestamp"), default=timezone.now)
|
||||
name = models.CharField(_('Name'), max_length=255, default='')
|
||||
email = models.EmailField(_('Email'), default='')
|
||||
timestamp = models.DateTimeField(_('Timestamp'), default=timezone.now)
|
||||
|
||||
def __str__(self):
|
||||
return "Message parent #{}".format(self.id)
|
||||
return 'Message parent #{}'.format(self.id)
|
||||
|
||||
|
||||
class Comment(CommentParent):
|
||||
@@ -76,13 +69,11 @@ class Comment(CommentParent):
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Kaehmykommentti")
|
||||
verbose_name_plural = _("Kaehmykommentit")
|
||||
verbose_name = _('Kaehmykommentti')
|
||||
verbose_name_plural = _('Kaehmykommentit')
|
||||
|
||||
message = models.TextField(_("Message"))
|
||||
parent = models.ForeignKey(
|
||||
"CommentParent", related_name="messages", on_delete=models.CASCADE
|
||||
)
|
||||
message = models.TextField(_('Message'))
|
||||
parent = models.ForeignKey('CommentParent', related_name='messages', on_delete=models.CASCADE)
|
||||
|
||||
|
||||
class Application(CommentParent):
|
||||
@@ -91,36 +82,34 @@ class Application(CommentParent):
|
||||
|
||||
Allows user to choose from existing roles or to create custom ones.
|
||||
"""
|
||||
|
||||
YEAR_CHOICES = (
|
||||
(1, "1"),
|
||||
(2, "2"),
|
||||
(3, "3"),
|
||||
(4, "4"),
|
||||
(5, "N"),
|
||||
(1, '1'),
|
||||
(2, '2'),
|
||||
(3, '3'),
|
||||
(4, '4'),
|
||||
(5, 'N'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Kaehmylomake")
|
||||
verbose_name_plural = _("Kaehmylomakkeet")
|
||||
verbose_name = _('Kaehmylomake')
|
||||
verbose_name_plural = _('Kaehmylomakkeet')
|
||||
|
||||
phone_number = models.CharField(_("Phone number"), max_length=10, default="")
|
||||
year = models.IntegerField(_("Year"), choices=YEAR_CHOICES)
|
||||
text = models.TextField(_("Text"), default="", max_length=300)
|
||||
phone_number = models.CharField(
|
||||
_('Phone number'), max_length=10, default="")
|
||||
year = models.IntegerField(_('Year'), choices=YEAR_CHOICES)
|
||||
text = models.TextField(_('Text'), default="", max_length=300)
|
||||
custom_role_name = models.CharField(
|
||||
_("Custom role name"), max_length=255, blank=True
|
||||
)
|
||||
custom_role_is_board = models.BooleanField(_("Board member"), blank=True)
|
||||
_('Custom role name'), max_length=255, blank=True)
|
||||
custom_role_is_board = models.BooleanField(
|
||||
_('Board member'), blank=True)
|
||||
custom_roles = models.ManyToManyField(
|
||||
"kaehmy.CustomRole", related_name="forms", blank=True
|
||||
)
|
||||
'kaehmy.CustomRole', related_name='forms', blank=True)
|
||||
preset_roles = models.ManyToManyField(
|
||||
"kaehmy.PresetRole", related_name="forms", blank=True
|
||||
)
|
||||
'kaehmy.PresetRole', related_name='forms', blank=True)
|
||||
|
||||
def __str__(self):
|
||||
"""Return model info."""
|
||||
return _("Kaehmy application: {}").format(self.name)
|
||||
return _('Kaehmy application: {}').format(self.name)
|
||||
|
||||
def comment_count(self):
|
||||
"""Count comments for kaehmy."""
|
||||
@@ -142,27 +131,34 @@ class Application(CommentParent):
|
||||
presets = [r.name.capitalize() for r in self.preset_roles.filter(is_board=True)]
|
||||
customs = [r.name.capitalize() for r in self.custom_roles.filter(is_board=True)]
|
||||
combined = presets + customs
|
||||
return _("Board: {}").format(", ".join(combined)) if len(combined) > 0 else ""
|
||||
return _('Board: {}').format(', '.join(combined)) if len(combined) > 0 else ''
|
||||
|
||||
def official_roles(self):
|
||||
presets = [
|
||||
r.name.capitalize() for r in self.preset_roles.filter(is_board=False)
|
||||
]
|
||||
customs = [
|
||||
r.name.capitalize() for r in self.custom_roles.filter(is_board=False)
|
||||
]
|
||||
presets = [r.name.capitalize() for r in self.preset_roles.filter(is_board=False)]
|
||||
customs = [r.name.capitalize() for r in self.custom_roles.filter(is_board=False)]
|
||||
combined = presets + customs
|
||||
return (
|
||||
_("Official: {}").format(", ".join(combined)) if len(combined) > 0 else ""
|
||||
)
|
||||
return _('Official: {}').format(', '.join(combined)) if len(combined) > 0 else ''
|
||||
|
||||
def all_roles(self):
|
||||
presets = [r.name.capitalize() for r in self.preset_roles.all()]
|
||||
customs = [r.name.capitalize() for r in self.custom_roles.all()]
|
||||
combined = presets + customs
|
||||
return ", ".join(combined) if len(combined) > 0 else ""
|
||||
return ', '.join(combined) if len(combined) > 0 else ''
|
||||
|
||||
def has_any_board_role(self):
|
||||
return self.preset_roles.filter(
|
||||
is_board=True
|
||||
).exists() or self.custom_roles.filter(is_board=True)
|
||||
return self.preset_roles.filter(is_board=True).exists() or self.custom_roles.filter(is_board=True)
|
||||
|
||||
|
||||
# Telegram channel entry for Kaehmys
|
||||
class TelegramChannel(models.Model):
|
||||
"""Model containing the channel id of a Telegram chat"""
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Telegram channel')
|
||||
verbose_name_plural = _('Telegram channels')
|
||||
|
||||
name = models.CharField(max_length=255)
|
||||
channel_id = models.CharField(max_length=255, unique=True)
|
||||
|
||||
def __str__(self):
|
||||
return 'Telegram channel: "{}"'.format(self.name)
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
body {
|
||||
max-width: 1000px;
|
||||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
}
|
||||
|
||||
div.tooltip-inner {
|
||||
max-width: 25rem;
|
||||
}
|
||||
@@ -22,10 +28,6 @@ div.tooltip-inner {
|
||||
.kaehmy-content {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
max-width: 1000px;
|
||||
width: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
p {
|
||||
|
||||
@@ -3,9 +3,13 @@
|
||||
}
|
||||
|
||||
footer {
|
||||
/* position: absolute; */
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
margin: 1rem;
|
||||
height: 60px; /* Set the fixed height of the footer here */
|
||||
/* line-height: 60px; /* Vertically center the text there */
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
footer .container .col .nav .nav-item {
|
||||
@@ -22,7 +26,6 @@ footer .container .col .nav .nav-item {
|
||||
|
||||
.lang-select {
|
||||
width: 10rem;
|
||||
margin-bottom: 1rem;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +1,37 @@
|
||||
.kaehmy-header {
|
||||
background-color: #0c2938;
|
||||
.header-content {
|
||||
|
||||
}
|
||||
|
||||
.kaehmy-header-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.header-content .logo {
|
||||
|
||||
}
|
||||
|
||||
.header-content .logo img {
|
||||
display: block;
|
||||
height: auto;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.kaehmy-banner {
|
||||
max-width: 1000px;
|
||||
width: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.kaehmy-banner-image {
|
||||
max-height: 10rem;
|
||||
max-width: 100%;
|
||||
@media screen and (min-width: 1000px) {
|
||||
.kaehmy_header-content {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background-color: #052f5f;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.kaehmy_header {
|
||||
margin-bottom: 331px;
|
||||
}
|
||||
}
|
||||
|
||||
.heading {
|
||||
display: flex;
|
||||
place-content: center;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
margin: 1rem;
|
||||
}
|
||||
.kaehmy-banner-image {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
.kaehmy_navigation {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.navbar-border {
|
||||
border-bottom: 2px solid #282b3b;
|
||||
}
|
||||
|
||||
.navbar-light .navbar-nav .nav-link {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
max-width: 1000px;
|
||||
width: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 192 KiB |
+3
-9
@@ -1,6 +1,6 @@
|
||||
import django_tables2 as tables
|
||||
from django.db.models import Count, Q
|
||||
from django.utils.translation import gettext as _
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from kaehmy.models import Application
|
||||
|
||||
@@ -8,12 +8,6 @@ from kaehmy.models import Application
|
||||
class ExportTable(tables.Table):
|
||||
class Meta:
|
||||
model = Application
|
||||
exclude = [
|
||||
"text",
|
||||
"messageparent_ptr",
|
||||
"custom_role_name",
|
||||
"custom_role_is_board",
|
||||
"timestamp",
|
||||
]
|
||||
exclude = ['text', 'messageparent_ptr', 'custom_role_name', 'custom_role_is_board', 'timestamp']
|
||||
|
||||
all_roles = tables.Column(verbose_name=_("Roles"), orderable=False)
|
||||
all_roles = tables.Column(verbose_name=_('Roles'), orderable=False)
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
{% block body %}
|
||||
|
||||
{% block header %}
|
||||
<div class="kaehmy-header">
|
||||
{% include "kaehmy/header.html" %}
|
||||
<div class="kaehmy_header">
|
||||
{% include "kaehmy:header.html" %}
|
||||
</div>
|
||||
{% endblock header %}
|
||||
|
||||
{% block navigation %}
|
||||
{% include "kaehmy/navigation.html" %}
|
||||
{% include "kaehmy:navigation.html" %}
|
||||
{% endblock %}
|
||||
|
||||
<div class="kaehmy-content">
|
||||
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
<div class="footer">
|
||||
{% block footer %}
|
||||
{% include "footer.html" %}
|
||||
{% include "kaehmy:footer.html" %}
|
||||
{% endblock footer %}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>
|
||||
{% if wrap_label %}
|
||||
<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>{% endif %}
|
||||
{% include "django/forms/widgets/input.html" %}
|
||||
{{ widget.label }}</label>
|
||||
{% if wrap_label %} {{ widget.label }}</label>{% endif %}
|
||||
<span class="fa fa-info-circle" data-toggle="tooltip" data-placement="right" title="{{ widget.description }}"></span>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "kaehmy/base.html" %}
|
||||
{% extends "kaehmy:base.html" %}
|
||||
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "kaehmy/base.html" %}
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
@@ -18,7 +18,7 @@
|
||||
<h4>{% trans "Non-board applications" %}</h4>
|
||||
{{ non_board_table|safe }}
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<a href="/kaehmy" class="btn btn-primary">{% trans "Front page" %}</a>
|
||||
</div>
|
||||
@@ -0,0 +1,24 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load staticfiles %}
|
||||
|
||||
<footer style="text-align: center">
|
||||
<div>
|
||||
<form class="lang-form form" action="{% url 'set_language' %}" method="post">{% csrf_token %}
|
||||
<span>
|
||||
<input name="next" type="hidden" value="{{ redirect_to }}" />
|
||||
<select onchange="this.form.submit()" class="lang-select form-control" name="language">
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
{% get_available_languages as LANGUAGES %}
|
||||
{% get_language_info_list for LANGUAGES as languages %}
|
||||
{% for language in languages %}
|
||||
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
|
||||
{{ language.name_local }} ({{ language.code }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</span>
|
||||
</form>
|
||||
<span>{% trans "Copyright Aalto-yliopiston Sähköinsinöörikilta ry" %} {% now 'Y' %}</span>
|
||||
</div>
|
||||
</footer>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user