452 Commits

Author SHA1 Message Date
Aarni Halinen 44e63b6105 Fix tests 2022-10-24 21:46:08 +03:00
Aarni Halinen dae78ee976 Add tests 2022-10-24 21:43:29 +03:00
Aarni Halinen f813eaf9bf Update node to v16 in CI 2022-10-24 21:43:29 +03:00
Aarni Halinen 8baea20824 Add .nvmrc 2022-10-24 21:43:29 +03:00
Aarni Halinen 393ee997d9 Add delete path for SignupViewSet 2022-10-24 21:43:29 +03:00
Aarni Halinen f67ce55d60 Fix webapp test as module 2022-10-24 21:42:41 +03:00
Ojakoo 5e1390ab6b Changes to kaehmy categories 2022-10-05 14:18:28 +03:00
Ojakoo 99348dc297 Update dparse 2022-10-05 12:58:29 +03:00
Ojakoo 715b309c89 Added new categories for kaehmy 2022-10-05 12:43:52 +03:00
Ojakoo 70676d5203 Lint 2022-09-23 20:53:25 +03:00
Ojakoo 037e4ae6e8 Use html templates for kaehmy emails. No translations for now. 2022-09-23 20:30:08 +03:00
Ojakoo 8d6f13b61d Update privacy policy link 2022-09-23 17:57:36 +03:00
Ojakoo 03982ee620 Update kaehmy dates and change header to html instead of picture. 2022-09-23 17:51:01 +03:00
Ojakoo a8923b63d6 wrap private key 2022-09-13 13:31:58 +03:00
Ojakoo 19975877cb Remove debug stuff 2022-09-13 09:59:55 +03:00
Ojakoo 2e0fad4bb2 Use POSIX format for source 2022-09-12 22:42:01 +03:00
Ojakoo f0179c1840 Change google creds format. Ugly but works. 2022-09-12 22:38:18 +03:00
Ojakoo 37a9750d4d Add group key and dev secrets to stack compose 2022-09-05 13:38:26 +03:00
Ojakoo 5575186570 lint 2022-09-05 12:03:31 +03:00
Ojakoo 9e179d5e06 Added error handling for incorrectly formatted google creds 2022-09-05 10:40:29 +03:00
Ojakoo ea9a732803 Modified paths 2022-08-17 23:00:11 +03:00
Ojakoo 0026b788b2 juuh eli 2022-08-16 23:00:36 +03:00
Ojakoo da3a484f6c wbn? 2022-08-16 22:24:20 +03:00
Ojakoo a310d51f5e or not 2022-08-16 21:43:04 +03:00
Ojakoo 6732e30213 #14 fix paths 2022-08-16 21:26:55 +03:00
Ojakoo 4e59eee200 Enable GOOGLE_SERVICE_ACCOUNT, should work now.. 2022-08-16 20:59:19 +03:00
Aarni Halinen bb0b2a2628 Fix kaehmy form 2022-08-11 11:28:30 +03:00
Aarni Halinen 32d636d3ee Disable GOOGLE_SERVICE_ACCOUNT for debugging 2022-08-11 09:52:58 +03:00
Ojakoo c91b99cdb1 lol 2022-08-09 21:44:01 +03:00
Aarni Halinen ca73eba609 Revert "Install django-suit v2"
This reverts commit fe46d57108.
2022-08-06 16:26:15 +03:00
Aarni Halinen fe46d57108 Install django-suit v2 2022-08-06 16:02:58 +03:00
Ojakoo 70e1835a4f Update dependencies 2022-08-06 12:28:43 +03:00
Ojakoo b7f17671d9 Resolve conlict 2022-08-06 12:09:50 +03:00
Ojakoo 34659403a8 lint 2022-08-06 11:03:39 +03:00
Ojakoo 4fbf5fe0a4 Jas list error notification only in prod 2022-08-06 10:17:43 +03:00
Ojakoo c6be0e6562 Add google envs to deploy 2022-08-06 10:07:00 +03:00
Aarni Halinen 3623c7e9f4 Fix TG message template 2022-08-04 00:51:45 +03:00
Aarni Halinen 298db5b78e Merge branch 'remove-baserole' into 'develop'
Remove webapp BaseRole model

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!67
2022-08-03 20:44:55 +00:00
Aarni Halinen 1ca6de3090 Fix lint 2022-08-03 23:35:41 +03:00
Aarni Halinen 07d0f2aa47 Remove comment 2022-08-03 23:19:42 +03:00
Aarni Halinen 93e122b8a8 Remove webapp BaseRole 2022-08-03 23:19:40 +03:00
Aarni Halinen 9678b663a0 Update kaehmy BaseRole 2022-08-03 23:18:32 +03:00
Aarni Halinen 992a2ec8e0 Add new BaseRole 2022-08-03 23:15:12 +03:00
Aarni Halinen b41bd41a54 Fix typo 2022-08-03 23:11:49 +03:00
Aarni Halinen 30f59c36fb Clean-up templates 2022-08-03 22:57:19 +03:00
Aarni Halinen f51d71e045 Fix most of html templates 2022-08-03 22:46:14 +03:00
Ojakoo 9c66238b82 Remove old json route 2022-08-03 00:34:42 +03:00
Ilari Ojakorpi 2f0143a9ae Merge branch 'update-django' into 'develop'
Update Django

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!68
2022-08-02 21:31:05 +00:00
Ojakoo 45ff2c3757 Format datetime to local timezone 2022-08-03 00:20:23 +03:00
Aarni Halinen 321d45b628 Fix unit tests 2022-08-01 22:31:47 +03:00
Aarni Halinen 2628d753f5 Fix JSONField deprecation warning 2022-08-01 22:22:30 +03:00
Aarni Halinen a603e2dff8 Add explicit primary keys 2022-08-01 22:22:02 +03:00
Aarni Halinen 96e05d908d Fix static files 2022-08-01 22:00:12 +03:00
Aarni Halinen a5bf5668eb Remove django-suit 2022-08-01 21:50:07 +03:00
Aarni Halinen 1ec5082faf Update Django packages 2022-08-01 21:33:31 +03:00
Aarni Halinen 6da5b97e19 Remove template tags removed in Django v3 2022-08-01 21:30:17 +03:00
Aarni Halinen 7fd30e3eba Update DB settings for Django v3 2022-08-01 21:24:10 +03:00
Aarni Halinen 3e9084ca1d Update jsonschema 2022-08-01 21:16:56 +03:00
Aarni Halinen a28f82d31e Update whitenoise 2022-08-01 21:15:08 +03:00
Aarni Halinen 04ecb8fc7e Update requests 2022-08-01 21:14:47 +03:00
Aarni Halinen 3e707e58a5 Run poetry update 2022-08-01 21:12:17 +03:00
Aarni Halinen 70d7f55996 Remove nose test runner 2022-08-01 21:09:17 +03:00
Aarni Halinen e408809e58 Update dev tools 2022-08-01 21:02:15 +03:00
Ojakoo 33fd4012f1 #13 fixed homepage link 2022-08-01 00:24:34 +03:00
Ojakoo 228938b695 lint 2022-07-31 16:33:28 +03:00
Ojakoo 72e91e3d62 Moved google creds to .env 2022-07-31 16:28:57 +03:00
Ojakoo 3f6a719e9d Added error handling, send email to user if adding fails 2022-07-31 11:12:51 +03:00
Aarni Halinen 490b99a848 Fix schema type 2022-07-28 22:13:01 +03:00
Aarni Halinen 0a899f5600 Fix schema 2022-07-28 21:45:48 +03:00
Aarni Halinen 7825cc7293 Add form validation on update 2022-07-28 21:16:22 +03:00
Aarni Halinen 8bb6e9e9a7 Add input validation schema for SignupFormViewSet create 2022-07-28 21:13:09 +03:00
Ojakoo 53c3acd39f directory api working state 2022-07-25 20:12:32 +03:00
Aarni Halinen dd0254a08e fix try-catch for jwt verification 2022-07-24 21:02:35 +03:00
Aarni Halinen 9b53fb4bc0 use jwt_access cookie in Filebrowser auth 2022-07-24 20:53:21 +03:00
Aarni Halinen e17c3ad92c Add --without-hashes to poetry export in Dockerfile 2022-07-22 01:08:36 +03:00
Aarni Halinen 362d981532 Disable pip dependency resolver in Dockerfile (handled by poetry) 2022-07-22 00:55:19 +03:00
Aarni Halinen e12be3c2f6 Revert "Add --without-hashes to poetry export in Dockerfile"
This reverts commit 3edae7f967.
2022-07-22 00:54:38 +03:00
Aarni Halinen 3edae7f967 Add --without-hashes to poetry export in Dockerfile 2022-07-22 00:52:04 +03:00
Aarni Halinen 4d159b2793 Add algorithms for FileBrowser JWT verification 2022-07-22 00:13:37 +03:00
Aarni Halinen cb3b831f7a Use husky for pre-commit hook 2022-07-21 23:47:56 +03:00
Aarni Halinen acba330694 Update CORS setting 2022-07-21 23:37:32 +03:00
Aarni Halinen eb22368055 Cleanup settings 2022-07-21 23:28:06 +03:00
Ojakoo fac2f9b367 Added pre-commit linter, lint 2022-07-04 10:33:38 +03:00
Ojakoo 7319c32d73 lint 2022-07-04 10:24:53 +03:00
Ojakoo b3a484ce55 Added verify path 2022-07-04 10:19:40 +03:00
Ojakoo 337b774074 lint 2022-06-21 21:47:54 +03:00
Ojakoo e70e598c57 #12 Changed djangorestframework-jwt to djangorestframework-simplejwt 2022-06-21 21:35:59 +03:00
Ojakoo 5eef2f685c Update pyjwt 2022-06-20 22:17:29 +03:00
Ojakoo d1953ef24c Changed application accept email formatting to html 2022-06-20 16:44:06 +03:00
Ojakoo ec4317d9e7 heevi email fix 2022-05-25 19:13:16 +03:00
Ojakoo cff84816fc Updated member application email links 2022-05-23 01:53:05 +03:00
Ojakoo 7881a24eb1 Updated heevi email message 2022-05-23 01:52:30 +03:00
Aarni Halinen a0765ca18b Merge branch 'python-dotenv' into 'develop'
Python dotenv

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!56
2022-05-19 19:23:14 +00:00
Aarni Halinen 913eb1cedf load .env file 2022-05-19 22:19:07 +03:00
Aarni Halinen 7035ebccca install python-dotenv 2022-05-19 22:18:50 +03:00
Aarni Halinen 2031146fc7 Merge branch 'add-dockerignore' into 'develop'
Add dockerignore file

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!59
2022-05-19 19:15:11 +00:00
Aarni Halinen 35f30300b3 Merge branch 'remove-about-page' into 'develop'
Remove about page

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!58
2022-05-19 19:13:58 +00:00
Aarni Halinen 342f2862a5 force empty logs and media folders 2022-05-19 22:11:36 +03:00
Aarni Halinen 3536ca5922 add dockerignore 2022-05-19 22:11:36 +03:00
Aarni Halinen 50ab7bc1f9 remove dealer 2022-05-19 22:09:47 +03:00
Aarni Halinen 102d8f82d6 remove about page 2022-05-19 22:08:18 +03:00
Aarni Halinen f302c0a17d Merge branch 'fix-audit' into 'develop'
Fix audit

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!57
2022-05-19 18:52:17 +00:00
Aarni Halinen a2b7086e9a update poetry to 1.1.13 2022-05-19 21:44:15 +03:00
Aarni Halinen 704652c643 update linter 2022-05-19 21:37:54 +03:00
Aarni Halinen 8741f6b113 update Pillow 2022-05-19 21:33:19 +03:00
Aarni Halinen 9ffb79aa52 poetry update 2022-05-19 21:32:37 +03:00
Ojakoo a2551cc110 Changed heevi banner 2022-05-19 15:44:32 +03:00
Aarni Halinen 8d7bd7067e remove TG_BOT_TOKEN 2022-01-14 00:14:32 +02:00
Aarni Halinen db3e3ae291 remove TG_BOT_TOKEN 2022-01-14 00:14:13 +02:00
Aarni Halinen 1afd476c18 fix lint 2022-01-13 23:37:17 +02:00
Aarni Halinen 322cc9d6fb add missing migration 2022-01-13 23:31:06 +02:00
Aarni Halinen 5f9a7a6994 poetry update 2022-01-13 23:25:14 +02:00
Aarni Halinen 3809ba9726 Merge branch 'feature/webhooks' into 'develop'
Feature: Webhooks

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!53
2022-01-13 21:10:42 +00:00
Aarni Halinen 79cc0bcd55 fix lint 2022-01-13 23:07:11 +02:00
Aarni Halinen 7cb03d40d4 fix ci 2022-01-13 23:02:57 +02:00
Aarni Halinen 34faf53347 Merge branch 'develop' into feature/webhooks 2022-01-13 22:58:10 +02:00
Aarni Halinen 8d2a26a9d1 Merge branch 'chore/black' into 'develop'
Chore: Add black as formatter

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!54
2022-01-13 20:55:55 +00:00
Aarni Halinen a05edb73d3 fix ci 2022-01-13 22:51:42 +02:00
Aarni Halinen 2c69e1b945 rename README 2022-01-13 22:39:52 +02:00
Aarni Halinen 9cae6ea890 update readme 2022-01-13 22:38:50 +02:00
Aarni Halinen 49a3b1449e fix ci 2022-01-13 22:38:40 +02:00
Aarni Halinen 02634c8e02 Merge branch 'develop' into feature/webhooks 2022-01-13 22:18:59 +02:00
Aarni Halinen 11efcdd579 format files with black 2022-01-13 22:10:51 +02:00
Aarni Halinen a0f062c697 replace pycodestyle with black 2022-01-13 22:10:44 +02:00
Aarni Halinen 1dc5d45e96 Fix ohlhafv email 2022-01-13 21:55:54 +02:00
Aarni Halinen 7382c4e4bf update send email uses 2022-01-13 21:53:55 +02:00
Aarni Halinen 867996ae27 fix lint 2022-01-13 21:00:42 +02:00
Aarni Halinen 798c860091 Add on save hooks for events, feed and jobads 2022-01-13 01:41:26 +02:00
Aarni Halinen 86a65e4680 Locale changes 2022-01-13 00:08:12 +02:00
Aarni Halinen 4a530826a8 Rewrite TG integration, support for other webhooks 2022-01-13 00:05:02 +02:00
Aarni Halinen 6f316401f7 Run poetry update 2022-01-12 21:13:24 +02:00
Aarni Halinen 8a8820be2f remove sentry tracing 2021-12-03 02:15:31 +02:00
Aarni Halinen 51278fd8bc try without special character 2021-11-17 20:14:35 +02:00
Aarni Halinen 2d86d548a3 update sendgrid code 2021-11-17 19:59:27 +02:00
Aarni Halinen 6380d39afb disable sentry performance monitoring 2021-11-17 19:23:28 +02:00
Toni Lyttinen 9420b1fd05 refactor: use jpg for banner 2021-11-17 16:13:09 +00:00
Toni Lyttinen fcd23c07ab "refactor": change banner to jpeg 2021-11-17 16:11:14 +00:00
Toni Lyttinen c92193057c style: change nav color 2021-11-17 15:41:57 +00:00
Toni Lyttinen a845be5394 fix: update header image src path 2021-11-17 15:25:59 +00:00
Oskari Ponkala c4a0f5a0ea change file ending 2021-11-17 15:16:57 +00:00
Oskari Ponkala 14735d8898 Replace heevit.svg 2021-11-17 15:13:38 +00:00
Oskari Ponkala d8250c691c update heevit 2021-11-17 14:59:57 +00:00
Toni Lyttinen 87d14240cf update 2021 heevi colors for banner 2021-11-17 14:56:55 +00:00
Aarni Halinen 50d4006b96 Merge branch 'sentry' into 'develop'
Feature: Sentry

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!48
2021-11-11 21:57:56 +00:00
Aarni Halinen d9b006904a unify string chars 2021-11-11 23:38:43 +02:00
Aarni Halinen 3aa225f3cd setup environment variables 2021-11-11 23:31:30 +02:00
Aarni Halinen a4e1aa5032 install sentry 2021-11-11 23:28:34 +02:00
Aarni Halinen 912ce44513 Update packages 2021-10-04 12:42:51 +03:00
Oskari Ponkala 6d093af511 Replace kaehmy_banner.png with updated dates 2021-10-04 06:49:09 +00:00
Oskari Ponkala be7a5fe2e2 Update kaehmy.html, update kähmy dates 2021-10-04 06:48:35 +00:00
Oskari Ponkala 40f6558a6e Update kaehmy.html 2021-10-04 06:47:29 +00:00
Toni Lyttinen 808c27e104 Merge branch 'feature/template-questions' into 'develop'
Feature: Signupform template questions API

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!46
2021-09-20 22:27:11 +00:00
Aarni Halinen b8342ba66a finish template question API 2021-08-24 02:50:46 +03:00
Aarni Halinen 9d05cd8290 poetry update 2021-08-23 19:37:08 +03:00
Aarni Halinen 4860c0a0d9 update npm deps 2021-06-15 22:58:33 +03:00
Oskari Ponkala 9d4c041153 Merge branch 'signups-fix' into 'develop'
Remove signup filtering by end time

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!44
2021-05-30 11:58:16 +00:00
Oskari Ponkala ce72ff7801 Remove signup filtering by end time 2021-05-30 11:53:45 +00:00
Aarni Halinen fb3328cb23 set DB on sslmode=require on prod 2021-05-16 18:42:58 +03:00
Aarni Halinen dac0fa1953 update production DB to Azure managed 2021-05-16 17:46:13 +03:00
Aarni Halinen 63672328f0 Merge branch 'feature/migrate-to-sendgrid' into 'develop'
Feature: Replace Mailjet with SendGrid

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!42
2021-05-16 14:30:07 +00:00
Aarni Halinen a927ab8a13 clean EMAIL_API_SECRETs 2021-05-16 17:24:13 +03:00
Aarni Halinen c6374d88b6 replace mailjet with sendgrid 2021-05-16 16:55:06 +03:00
Aarni Halinen f72017df01 Merge branch 'refactor/clean-deps' into 'develop'
Refactor: Use poetry for deps, update & audit dependencies

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!35
2021-05-08 16:21:24 +00:00
Aarni Halinen 26635bbbcc update metadata 2021-05-08 18:56:38 +03:00
Aarni Halinen 86973562ff update Django 2021-05-08 18:55:06 +03:00
Aarni Halinen 0c1bdba358 fix coverage report 2021-05-08 18:50:08 +03:00
Aarni Halinen 8481e963a3 update readme 2021-04-14 17:48:45 +03:00
Aarni Halinen 39ba51f11a Dockerfile from slim Debian image, remove poetry from server image 2021-04-14 17:33:50 +03:00
Aarni Halinen d889df951c install build-essential on Dockerfile 2021-04-14 17:20:15 +03:00
Aarni Halinen 19f8ff006c update gunicorn 2021-04-14 17:06:18 +03:00
Aarni Halinen ed77526f5c add audit step 2021-04-14 17:06:18 +03:00
Aarni Halinen 6f6dc09f01 install dev deps on test step 2021-04-14 17:06:18 +03:00
Aarni Halinen 5049ef415d update python to 3.9, fix gitlab-ci test step 2021-04-14 17:06:18 +03:00
Aarni Halinen 441227bc15 remove requirements.txt files 2021-04-14 17:06:18 +03:00
Aarni Halinen 78575075e5 update Dockerfile to use poetry 2021-04-14 17:06:18 +03:00
Aarni Halinen 4029031bf0 update django-cors-headers 2021-04-14 17:06:18 +03:00
Aarni Halinen f1ea08bc30 remove old files 2021-04-14 17:06:18 +03:00
Aarni Halinen e68f04117d update deps, Django v2.2 2021-04-14 17:06:18 +03:00
Aarni Halinen d07914bdb5 clean unused imports 2021-04-14 17:06:18 +03:00
Aarni Halinen 38eb4cd9cd clean dependencies 2021-04-14 17:06:18 +03:00
Aarni Halinen ab104d2acd Merge branch 'oskari.ponkala-develop-patch-58256' into 'develop'
Changed old vtmk emails to new admin email addresses and fixed an old link to old web

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!40
2021-04-14 14:05:39 +00:00
Oskari Ponkala 0376acdc9e Changed old vtmk emails to new admin email addresses and fixed an old link to old web 2021-04-08 12:10:24 +00:00
Oskari Ponkala abbf23456b Merge branch 'oskari.ponkala-develop-patch-49436' into 'develop'
Fixed news feed sorting

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!38
2021-04-08 10:03:23 +00:00
Oskari Ponkala 6a156dd9db Fixed news feed sorting 2021-04-08 09:59:40 +00:00
Aarni Halinen 9fb9bd447a remove throttling 2021-03-31 19:52:47 +03:00
Aarni Halinen ba69f54465 Revert "restart policy unless-stopped"
This reverts commit 9a450ea44a.
2021-03-31 00:02:54 +03:00
Aarni Halinen 9a450ea44a restart policy unless-stopped 2021-03-30 23:21:47 +03:00
Aarni Halinen 242a45a92e deploy: change prod sub domain for live 2021-03-30 22:30:11 +03:00
Aarni Halinen ad472ca8ff reapply filter backends 2021-03-30 19:48:22 +03:00
Aarni Halinen 928a98904b reapply filter backends 2021-03-30 19:33:36 +03:00
Toni Lyttinen 2882c1a4c8 oops 2021-03-30 15:17:24 +00:00
Toni Lyttinen 1e0c050f99 fixed lint and raised pagination page size to 1000 ":D" 2021-03-30 15:06:55 +00:00
Toni Lyttinen 744874a54c comment out pagination 2021-03-30 10:50:20 +00:00
Aarni Halinen 4dfa033f0b fix pycodestyle config path 2021-03-29 20:50:16 +03:00
Aarni Halinen d56ab02c27 remove sahkopiikki related code 2021-03-29 20:18:03 +03:00
Aarni Halinen 01c5a0ed53 add poetry files 2021-03-29 01:13:44 +03:00
Aarni Halinen 06fcfcf3a1 remove coffee_scale 2021-03-29 00:55:52 +03:00
Aarni Halinen 6567cd87d5 update npm packages 2021-03-29 00:52:15 +03:00
Aarni Halinen 9efe0d7489 use node v14 2021-03-29 00:48:54 +03:00
Aarni Halinen d7b6ef3c72 Change sik.party addresses to sahkoinsinoorikilta.fi 2021-01-18 21:39:25 +02:00
Aarni Halinen 0a31492733 Fix queryset filtering on new soft deletes 2021-01-15 00:27:41 +02:00
Aarni Halinen c96ff2f112 Merge branch 'feature/delete-apis' into 'develop'
Feature: Add delete endpoints for common models

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!33
2021-01-14 20:37:51 +00:00
Aarni Halinen 2b0d1c6ca7 Fix str format 2021-01-14 22:34:30 +02:00
Aarni Halinen 90342011f5 Add deleted prefix for deleted instances 2021-01-14 22:26:51 +02:00
Aarni Halinen 27f9c79cf3 Test Feed delete 2021-01-14 22:12:34 +02:00
Aarni Halinen de1ba7c9f2 Add soft delete functionality for Event, SignupForm, Feed & JobAd 2021-01-14 22:05:08 +02:00
Aarni Halinen c2fb77816a Order SignupForms by start_time 2021-01-14 21:26:28 +02:00
Aarni Halinen 4be0b75a1c Update production Docker secret names 2020-12-12 14:01:07 +02:00
Aarni Halinen 09aa42cfb8 Merge branch 'feature/signup-improvements' into 'develop'
Feature/signup improvements

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!31
2020-11-30 19:13:27 +00:00
Aarni Halinen c5008e3656 Add debug logging 2020-11-30 20:54:31 +02:00
Aarni Halinen e5224fe1e2 Lint fix 2020-11-23 20:23:44 +02:00
Aarni Halinen b834567309 API for getting signups for list view 2020-11-08 00:16:29 +02:00
Aarni Halinen b1921d4926 Add API for sending emails for signupees 2020-11-07 21:32:49 +02:00
Aarni Halinen 00c6920c8a Add test for editing signup with uuid 2020-11-07 21:00:55 +02:00
Aarni Halinen 1fe323cbdd Add soft delete API for signups 2020-11-07 20:40:59 +02:00
Aarni Halinen 3a58ff0ef1 Merge branch 'feature/remove-officials' into 'develop'
Remove Officials

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!30
2020-11-07 18:39:58 +00:00
Aarni Halinen d06c24bde0 Remove Officials 2020-11-07 18:39:58 +00:00
Aarni Halinen 2359743b20 Update pycodestyle 2020-11-07 19:28:59 +02:00
Aarni Halinen 437936ad64 Add inof about kaehmy limits 2020-11-07 17:13:35 +02:00
Toni Lyttinen 27dd56b6fb Merge branch 'feature/job-ads' into 'develop'
Add Job-ad DB object and serializer

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!29
2020-11-07 13:41:37 +00:00
Aarni Halinen ff5a92897c Authenticated filter for Job ads 2020-11-03 22:44:27 +02:00
Aarni Halinen e9c38d56d8 Compilemessages 2020-11-03 19:53:47 +02:00
Aarni Halinen 57bc12b621 Fix Feed test 2020-11-03 18:38:37 +02:00
Aarni Halinen 1ff188eddd Add Job-ads DB object and serializers 2020-11-03 18:27:46 +02:00
Toni Lyttinen b443b39457 added "toimikunta-appro" date to kaehmy.html 2020-10-28 13:29:44 +00:00
Toni Lyttinen 22615da3b4 updated kaehmy dates and deadlines kaehmy.html 2020-10-20 17:25:41 +00:00
Toni Lyttinen d09cbf3b2c changed deadline for board application kaehmy_banner.png 2020-10-20 17:23:33 +00:00
Aarni Halinen 66fcfab4da Revert "Use Docker:18 for CI/CD"
This reverts commit a7d0f49190.
2020-10-15 00:54:36 +03:00
Aarni Halinen a7d0f49190 Use Docker:18 for CI/CD 2020-10-15 00:35:09 +03:00
Aarni Halinen 519571f84c Remove filebrowser deployment 2020-10-15 00:03:25 +03:00
Aarni Halinen 30847e8bc2 Serialize location and title locales 2020-10-08 18:38:27 +03:00
Aarni Halinen df69a64ffc Merge branch 'feature/signup-email' into 'develop'
Feature/signup email HTML

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!25
2020-10-08 15:03:33 +00:00
Aarni Halinen 157f1c487c Get kahmyopas from static file server 2020-10-06 20:10:10 +03:00
Aarni Halinen 1e5a691d73 Send HTML email 2020-10-06 19:10:53 +03:00
Aarni Halinen 43fe25ba64 Get SignupForm email content from frontend 2020-10-06 18:33:41 +03:00
Toni Lyttinen 851269b13f fixed image src 2020-10-05 12:13:50 +00:00
Toni Lyttinen 0e5e18b035 Update kaehmy/static/kaehmy/img/kaehmy_banner.png, kaehmy/static/kaehmy/css/base.css, kaehmy/static/kaehmy/css/footer.css, kaehmy/static/kaehmy/css/header.css, kaehmy/static/kaehmy/css/nav.css, kaehmy/static/kaehmy/css/readme.md files 2020-10-05 12:12:02 +00:00
Toni Lyttinen f543e83e80 Update kaehmy/static/css/base.css, kaehmy/static/css/footer.css, kaehmy/static/css/header.css, kaehmy/static/css/nav.css, kaehmy/static/css/readme.md, kaehmy/static/img/kaehmy_banner.png files 2020-10-05 11:57:13 +00:00
Toni Lyttinen 0b72c20e32 more date fixing 2020-10-05 11:47:32 +00:00
Toni Lyttinen 04e6eb3ec2 changed dates on kaehmy.html 2020-10-05 11:22:54 +00:00
Toni Lyttinen 4e1efff7f2 changed kaehmy banner dates 2020-10-05 11:18:48 +00:00
Aarni Halinen d45f7195f5 Upgrade Pillow 2020-10-04 18:22:07 +03:00
Aarni Halinen dc6347a6eb Signup email and other model fields 2020-10-04 18:21:54 +03:00
Aarni Halinen 1304cffa2a Revert "SIK100 Seminar email hack"
This reverts commit 9b9406ab4b.
2020-09-22 17:58:33 +03:00
Aarni Halinen 656b2d1d95 Add missing envs 2020-08-31 03:59:03 +03:00
Aarni Halinen 9b9406ab4b SIK100 Seminar email hack 2020-08-31 03:56:37 +03:00
Aarni Halinen 3630b360d7 Test proxy configs to get API return https-urls 2020-08-29 14:48:58 +03:00
Aarni Halinen c6a3445774 Merge branch 'improve/serialization' into 'develop'
Hide invisible signups from event serialization

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!21
2020-08-06 19:06:19 +00:00
Aarni Halinen 9b7e9a6d92 Bubblegum fix for event test 2020-08-06 22:04:16 +03:00
Aarni Halinen 3de975203e Minor improvements 2020-08-06 20:59:26 +03:00
Aarni Halinen 38669947cd Some serialization BS 2020-08-04 01:04:38 +03:00
Aarni Halinen e03b6e6606 Try ordering via @property field order_by 2020-08-03 12:03:07 +03:00
Aarni Halinen 8491842a04 Try ordering via @property field order_by 2020-08-03 11:57:24 +03:00
Aarni Halinen eb80250335 Try SerializerMethodField for ordering list_names 2020-08-03 11:20:13 +03:00
Aarni Halinen dec348b032 Try ordering signups on pk 2020-07-31 16:59:27 +03:00
Aarni Halinen 5071bd6172 Serialize SignupForm visible 2020-07-24 23:31:35 +03:00
Aarni Halinen f98971270a New email secrets for prod 2020-07-24 22:21:44 +03:00
Aarni Halinen e2d55ffce2 Name fields 2020-07-24 00:41:07 +03:00
Aarni Halinen 37cd2eda4c Update tests 2020-07-24 00:16:10 +03:00
Aarni Halinen aa0ae01227 Move SignupForm validation schema building to front end 2020-07-23 23:50:12 +03:00
Aarni Halinen 54ded7d3f9 Add quota for SignupForms 2020-07-22 20:57:26 +03:00
Aarni Halinen ef38d8be46 Support uploading of images from event creation 2020-07-22 19:11:39 +03:00
Aarni Halinen 673bbc09eb Signup edit URL 2020-07-17 15:50:04 +03:00
Aarni Halinen 9fa62e9a2f Some cleaning 2020-07-16 13:57:12 +03:00
Aarni Halinen 32e0704f53 Add boolean for text vs html email 2020-07-16 13:56:54 +03:00
Aarni Halinen 39ab86fe3e Get first signup email through 2020-07-16 13:27:18 +03:00
Aarni Halinen f9e7c4a904 Update sikdata-mount.ps 2020-07-16 07:58:19 +00:00
Aarni Halinen 0adca6bd2e Mailjet 2020-07-16 01:50:46 +03:00
Aarni Halinen 5c4e4c7f5a Allow usage of normal ENVs for Docker setups 2020-07-16 01:35:11 +03:00
Aarni Halinen 76e1c71cd1 Use default Django secret key from settings.py 2020-07-16 00:48:01 +03:00
Aarni Halinen 67b31060b2 Fix deploy image 2020-07-16 00:31:12 +03:00
Aarni Halinen 110d426efd New Dev deploy 2020-07-16 00:21:18 +03:00
Aarni Halinen 62bb317c16 Add SIKData mount ps-script 2020-07-15 17:16:37 +03:00
Aarni Halinen a379390ca1 Very fast translations 2020-06-22 23:15:34 +03:00
Aarni Halinen 75ece86fb0 Signup email POC 2020-06-22 23:12:06 +03:00
Aarni Halinen 26af46fa12 UUID, email fields and receiver for sending them, /edit API for modifying signup with ID and UUID 2020-06-22 23:09:20 +03:00
Aarni Halinen 027b9c370c Clean test imports 2020-06-22 23:07:22 +03:00
Aarni Halinen 59c9fec51c Update CI 2020-06-16 23:05:11 +03:00
Aarni Halinen 98ea2d8a47 Update CI 2020-06-16 23:01:19 +03:00
Aarni Halinen 0b9aed128a Add identifying string to Signups and list them on Forms 2020-06-16 22:31:32 +03:00
Aarni Halinen 4ebba7f74a Cleaning imports 2020-06-16 19:23:25 +03:00
Aarni Halinen 11c36f2111 Merge branch 'feature/signup-validation' into 'develop'
Feature/signup validation

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!18
2020-06-16 16:18:58 +00:00
Aarni Halinen ef78ef9dcd Some linter setup 2020-06-16 19:18:11 +03:00
Aarni Halinen 34984d4a5e Clean test imports 2020-06-16 18:42:55 +03:00
Aarni Halinen 319324503f Split signup tests to 2 files 2020-06-16 17:57:48 +03:00
Aarni Halinen 683f50b199 CI updates 2020-06-16 17:39:03 +03:00
Aarni Halinen 2d6c474baa CI updates 2020-06-16 17:37:19 +03:00
Aarni Halinen b302681bbf Style fixes 2020-06-16 17:32:25 +03:00
Aarni Halinen 90d8ee2ea9 HTTP responses 2020-06-15 22:01:05 +03:00
Aarni Halinen f505fae3e6 Test signup POST 2020-06-15 20:25:24 +03:00
Aarni Halinen 2433c7828d Fix pattern generation 2020-06-15 20:02:01 +03:00
Aarni Halinen 0edde936cc Get jsonschema from questions JSON 2020-06-15 19:18:57 +03:00
Aarni Halinen 2e11621e5b Set Allow any for testing purposes 2020-06-15 19:18:24 +03:00
Aarni Halinen 5f706d9236 Install jsonschema 2020-06-15 19:17:33 +03:00
Aarni Halinen d3350c06e0 Minor CI changes 2020-03-09 18:08:43 +02:00
Aarni Halinen 648feaa762 Ignore admin.py and translation.py files on test coverage 2020-03-05 21:12:24 +02:00
Aarni Halinen 6d4662a1cb Add Django volumes to hidden directory on static file server 2020-03-05 21:00:20 +02:00
Aarni Halinen 523adcd256 Remove Pipenv possibility, bump deps 2020-02-22 19:45:28 +02:00
Aarni Halinen 5f0ab2ddbf Default PG password for dev and local development 2020-02-22 15:06:49 +02:00
Leo Lahti 6e3b3bd52f Bumped psycopg2 to 2.8.4 2020-02-22 14:38:38 +02:00
Toni Lyttinen 6dcbc1c652 Updated ohlhafv navbar color 2020-02-02 14:31:43 +00:00
Toni Lyttinen 80e00e2b05 Changed ohlhalv button hover color 2020-02-02 14:29:35 +00:00
Toni Lyttinen 80298d174f Updated challenge buttoncolor to match the page background 2020-02-02 14:15:00 +00:00
Aarni Halinen b790906b23 Fix year issue in contact tests 2020-02-02 15:31:16 +02:00
Toni Lyttinen e7164bb613 Update heevit.svg image to 2020 version 2020-02-02 13:29:15 +00:00
Toni Lyttinen a2a2b9830e Update background color to match the image 2020-02-02 13:28:41 +00:00
Aarni Halinen eb0bfdbd21 Move bad test fail one year forward 2020-02-02 15:26:29 +02:00
Toni Lyttinen d9116e51ca Updated background color to match image 2020-02-02 13:07:11 +00:00
Toni Lyttinen 3188a8e520 Replace heevit.svg 2020-02-02 13:01:42 +00:00
Toni Lyttinen a9c1d06ece Updated heevit.svg to 2020 version. 2020-02-02 12:59:23 +00:00
Aarni Halinen c32292c7d9 Allow api.sika.sik.party 2019-12-18 20:29:36 +02:00
Aarni Halinen f2ffcaa5a9 Parallelize CI a bit 2019-12-18 16:53:35 +02:00
Aarni Halinen d0216bb85c Update admin login url 2019-12-17 21:34:40 +02:00
Aarni Halinen 62263848f1 Add filebrowser and modify volume paths 2019-12-17 19:06:43 +02:00
Aarni Halinen 242199399b Use stop-first for PG update_config 2019-12-17 18:28:37 +02:00
Aarni Halinen 66d9ee0a75 Update backend postgres 2019-12-17 18:11:46 +02:00
Aarni Halinen c2222d4426 Fix lint 2019-11-22 18:09:53 +02:00
Aarni Halinen 368b28d241 Debug mode ALLOWED_HOSTS wildcard 2019-11-11 22:59:11 +02:00
Aarni Halinen c5df450a96 Minor import bug fix 2019-11-11 00:56:39 +02:00
Aarni Halinen c1e0a32a08 Merge branch 'feature/webapp-tests' into 'develop'
Feature/webapp tests

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!9
2019-11-10 22:55:40 +00:00
Aarni Halinen 373db50995 Minor fix to GET as admin 2019-11-11 00:47:22 +02:00
Aarni Halinen f422e31a42 Add some code to GET all objects as admin 2019-11-11 00:44:35 +02:00
Aarni Halinen f7cf54cab2 Fix webapp tests 2019-11-11 00:18:48 +02:00
Aarni Halinen d17e6710e6 Merge branch 'develop' into feature/webapp-tests 2019-11-10 21:38:07 +02:00
Aarni Halinen f64cec7d2e Fix Infernal Server Error on SignupForms 2019-11-10 21:34:10 +02:00
Aarni Halinen ff87767aab Merge branch 'develop' into feature/webapp-tests 2019-11-10 20:46:30 +02:00
Aarni Halinen 08ec9bb9cc Use json.loads for create and update of JSONField data 2019-11-10 20:46:16 +02:00
Aarni Halinen eb63c9fc1c Fix signup JSON form ID 2019-11-10 20:30:42 +02:00
Aarni Halinen eec157d7f3 Merge branch 'develop' into feature/webapp-tests 2019-11-10 20:28:06 +02:00
Jan Tuomi 1ce3c28e94 Use JSONField for signup and signupform models 2019-11-10 20:26:28 +02:00
Aarni Halinen 52a646fe04 Revert "Use JSONFields"
This reverts commit 5fba7d6249.
2019-11-10 20:25:21 +02:00
Aarni Halinen 8c4f6e886c New signup fixture 2019-11-10 20:13:27 +02:00
Aarni Halinen a9d6bf50d6 TemplateQuestion Tests 2019-11-10 20:00:13 +02:00
Aarni Halinen 8f0c9feecf Remove json dumps 2019-11-10 20:00:03 +02:00
Aarni Halinen 5fba7d6249 Use JSONFields 2019-11-10 19:24:18 +02:00
Aarni Halinen 3d755bca8c Remove translations from SignupFormSerializer 2019-11-10 19:19:08 +02:00
Aarni Halinen ff04e645e4 Merge branch 'develop' into feature/webapp-tests 2019-11-10 18:11:49 +02:00
Aarni Halinen 2c72e01353 Clean webapp views imports 2019-11-10 18:11:32 +02:00
Aarni Halinen 3836e00174 Merge branch 'develop' into feature/webapp-tests 2019-11-10 18:01:53 +02:00
Aarni Halinen b33f29d620 Merge branch 'feature_nginx_jwt_auth' into 'develop'
Add api with which nginx can authenticate users

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!8
2019-11-10 15:58:45 +00:00
Aarni Halinen 8afeec21cc Settings add localhost to allowed 2019-11-10 03:04:56 +02:00
Aarni Halinen 1778a0509c Minor fixes 2019-11-10 03:04:05 +02:00
Aarni Halinen 86ae2197f3 Contact API tests 2019-11-10 03:03:48 +02:00
Aarni Halinen 4859daf0b3 Clean old codebase from views.py 2019-11-10 02:00:45 +02:00
Aarni Halinen ff9cad1f47 Minor clean up 2019-11-10 01:58:32 +02:00
Aarni Halinen 01161db33b Test since query parameter 2019-11-10 01:58:15 +02:00
Aarni Halinen a7728e50dd Skeleton clean and small comment 2019-11-10 01:39:01 +02:00
Aarni Halinen edb6d73c06 Add signup create test skeleton 2019-11-10 01:36:08 +02:00
Aarni Halinen 0f50daf289 Singup get tests 2019-11-10 01:20:31 +02:00
Aarni Halinen f4b55b7863 Use month_from_now utility function in tests 2019-11-10 01:20:14 +02:00
Aarni Halinen 00de36ca44 Event CRUD tests 2019-11-09 23:58:21 +02:00
Jan Tuomi 59f47dec26 Update signup serializer for use with form ids 2019-11-09 23:51:13 +02:00
Aarni Halinen 8c4b997f0f Event test fixture 2019-11-09 23:13:56 +02:00
Aarni Halinen b2e2ff6699 Tag test fixture 2019-11-09 22:51:14 +02:00
Aarni Halinen ff98f700f9 Event POST unauthorized test 2019-11-09 22:19:06 +02:00
Aarni Halinen fe48a2a336 Event POST test 2019-11-09 22:18:19 +02:00
Aarni Halinen 80c8d9bc90 Fix inconsistency 2019-11-09 21:57:55 +02:00
Aarni Halinen 31ebace4cd Use context and other test fixes 2019-11-09 21:48:08 +02:00
Aarni Halinen b3a3a9815f Merge branch 'develop' into feature/webapp-tests 2019-11-09 21:15:29 +02:00
Aarni Halinen 81a16680e4 Ignore all .coverage files 2019-11-09 21:15:24 +02:00
Aarni Halinen 04e91a0e8d Check npm packages are OK 2019-11-09 21:14:02 +02:00
Aarni Halinen 72b2a18c63 Webapp test changes, GET for Event API 2019-11-09 21:09:25 +02:00
Antti Mäki 029089b32d Added language selection to the membership application form 2019-11-09 20:27:55 +02:00
Oksanen, Ilkka (Nokia - FI/Espoo) 3ea513785a Add api with which nginx can authenticate users 2019-11-09 16:55:12 +02:00
Aarni Halinen 8dc576983c Split tests.py into multiple files 2019-11-09 13:54:43 +02:00
Aarni Halinen a7ada33ff9 Open port 5432 on docker-compose 2019-11-09 13:41:46 +02:00
Aarni Halinen 59a59685d1 Merge branch 'feature/fix-official-models' into 'develop'
Fix Official models and API serializing

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!4
2019-11-03 15:58:18 +00:00
Aarni Halinen 2f0024630d Use IMAGE_NAME instead of removed dev image 2019-11-03 17:33:35 +02:00
Aarni Halinen e9706d0e4f Copy env file to dev deployment 2019-11-03 17:20:12 +02:00
Aarni Halinen baa329ad5d Run publish on dev branch 2019-11-03 17:13:08 +02:00
Aarni Halinen 389408a59e Merge branch 'feature/use-envs-for-settings' into 'develop'
Feature/use envs for settings

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!6
2019-11-03 15:09:41 +00:00
Aarni Halinen 2f4009d002 Update dev env 2019-11-03 17:06:24 +02:00
Aarni Halinen dab6f98ec2 Combine PROD and DEV images 2019-11-03 17:01:38 +02:00
Aarni Halinen b23e439eb5 Remove IS_DOCKER ENV 2019-11-03 16:55:36 +02:00
Aarni Halinen fdea5858ba Remove obsolete copy from setup.sh 2019-11-03 16:13:17 +02:00
Aarni Halinen 96fc8a5569 Update development docker setup 2019-11-03 16:06:14 +02:00
Aarni Halinen c408a6532e Add committee API 2019-10-10 20:48:39 +03:00
Aarni Halinen 09e973fab1 Allow missing official image 2019-10-10 20:48:27 +03:00
Aarni Halinen 971247e67c Show board status 2019-10-10 19:10:35 +03:00
Aarni Halinen 59dcb382ce Add images to Officials 2019-10-10 19:06:48 +03:00
Aarni Halinen 86af724ccc Add translations to API 2019-10-10 19:03:26 +03:00
Aarni Halinen e5cc62bbbf Use OccupationSerializer for the Contacts API 2019-10-10 18:38:45 +03:00
Aarni Halinen 4b63de6fc4 Merge branch 'develop' into feature/fix-official-models 2019-10-10 18:15:07 +03:00
Aarni Halinen d0d63a6e29 Add translations 2019-10-10 18:13:01 +03:00
Aarni Halinen d7f57c5b3a Clean webapp urls 2019-10-10 18:02:06 +03:00
Aarni Halinen 777cca4917 Merge branch 'develop' into feature/fix-official-models 2019-10-10 17:43:24 +03:00
Aarni Halinen e05fcb423c Fix bind mount paths 2019-10-10 01:05:06 +03:00
Aarni Halinen 7457b8a24d Add env var for unit tests 2019-10-10 00:56:44 +03:00
Aarni Halinen 8a3fdc40eb Merge branch 'develop' into feature/use-envs-for-settings 2019-10-10 00:43:48 +03:00
Aarni Halinen 192be6e28b Remove copy cmds from CI 2019-10-10 00:33:39 +03:00
Aarni Halinen 6ba91f1fab Add media bind mount for backend 2019-10-10 00:31:07 +03:00
Aarni Halinen 4725432c32 Merge branch 'feature/python-and-swarm-mods' into 'develop'
Update to python 3.7 and add deploy policies

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!5
2019-10-09 20:58:25 +00:00
Aarni Halinen 4e5b6c565a Update to python 3.7 and add deploy policies 2019-10-09 23:53:35 +03:00
Aarni Halinen 4e216891e6 Remove copy from Dockerfile.prod 2019-10-09 23:51:36 +03:00
Aarni Halinen 562738f945 Use env variables for settings.py variables 2019-10-09 23:49:57 +03:00
Aarni Halinen 7ffddf41f8 Fix some paths 2019-10-09 23:23:16 +03:00
Aarni Halinen 88f37928ac Downgrade postgres from 12 to 10 2019-10-09 22:21:46 +03:00
Antti Mäki fcd7ae7c04 Replace kahmyopas.pdf 2019-10-09 19:01:54 +00:00
Aarni Halinen 995d42640f Revert "Remove DB volume temporarly"
This reverts commit 046ae89d29.
2019-10-09 21:17:43 +03:00
Aarni Halinen 13583bd6ab Add depends on DB for backend 2019-10-09 19:33:22 +03:00
Aarni Halinen 522c804369 Remove DB name, use same as DB USER 2019-10-09 19:32:46 +03:00
Aarni Halinen 046ae89d29 Remove DB volume temporarly 2019-10-09 19:03:40 +03:00
Aarni Halinen 9ffec51537 Add ENV population to entrypoint.sh 2019-10-08 23:16:09 +03:00
Aarni Halinen c07ec5855a Revert "Read secrets from files to vars"
This reverts commit 84f1f9239b.
2019-10-08 23:07:45 +03:00
Aarni Halinen c445a4a66c Revert "Read ENV files runtime"
This reverts commit 96dd77e455.
2019-10-08 23:07:03 +03:00
Aarni Halinen 96dd77e455 Read ENV files runtime 2019-10-08 22:17:18 +03:00
Aarni Halinen 84f1f9239b Read secrets from files to vars 2019-10-08 20:54:54 +03:00
Aarni Halinen a24a7f02b9 Add _FILE ending for ENVs 2019-10-08 19:56:52 +03:00
Aarni Halinen d3c50dc608 Try other yml syntax 2019-10-08 19:42:49 +03:00
Aarni Halinen 34a4f027f5 Bump yml version 2019-10-08 13:04:30 +03:00
Aarni Halinen 66ed4c0713 Dockerize postgres 2019-10-08 12:41:40 +03:00
Aarni Halinen 1038e9c67e Fix syntax errors 2019-10-08 10:19:14 +03:00
Aarni Halinen 6a3398e8de Bump yml version 2019-10-08 10:01:19 +03:00
Aarni Halinen 8a87288d25 Add host network config 2019-10-08 09:40:53 +03:00
Aarni Halinen 1d4a509c7e Install whitenoise for serving static files 2019-10-08 09:34:56 +03:00
Aarni Halinen 29d2f6b90d Use Docker interface IP to access DB 2019-10-08 01:00:29 +03:00
Aarni Halinen 5d6cf1fb80 Add execution rights to entry scripåt 2019-10-08 00:46:37 +03:00
Aarni Halinen 95e72b7ab2 Add placeholder publish to fix dev server 2019-10-08 00:44:35 +03:00
Aarni Halinen d17d1fb1b7 Add entrypoint script 2019-10-08 00:29:20 +03:00
Aarni Halinen d0d822b8f4 Add network_mode host for DB access 2019-10-08 00:22:03 +03:00
Aarni Halinen f6abe6f999 Fix Prod container settings path 2019-10-08 00:04:59 +03:00
Aarni Halinen 8a5bbaa229 Remove docker-compose db volumes 2019-10-07 23:56:55 +03:00
Aarni Halinen 5750d62fe0 Fix production container 2019-10-07 23:53:07 +03:00
Aarni Halinen 71e683fbe3 Fix secret namespace 2019-10-07 22:03:05 +03:00
Aarni Halinen e214144ec3 Add external secrets 2019-10-07 21:28:16 +03:00
Aarni Halinen 0faf19e319 yml syntax fix 2019-10-07 21:19:05 +03:00
Aarni Halinen 10552c5f2a Try docker secrets 2019-10-07 21:10:19 +03:00
Aarni Halinen 4181b246a1 Add path to .ENV file on prod server 2019-10-07 20:17:02 +03:00
Aarni Halinen c39979e253 Use different python image 2019-10-07 20:16:35 +03:00
Aarni Halinen 807c599b85 Typo fix 2019-10-07 20:03:07 +03:00
Aarni Halinen 9e186c32ba Manual production deploy 2019-10-07 19:59:23 +03:00
Aarni Halinen e3d0709b31 Add new Dockerfile for prod 2019-10-07 19:56:55 +03:00
Aarni Halinen bd8fc6b404 New Sika Docker swarm deploy 2019-10-07 18:50:00 +03:00
Antti Mäki 8500ed8fbd Update header.css 2019-10-06 22:18:21 +00:00
Aarni Halinen cb86c9a2ae Fix Kaehmy PresetRole rendering by removing wrap_label from checkbox_option.html 2019-10-06 18:32:17 +03:00
Aarni Halinen bb0f4244c7 Update django-bootstrap3 2019-10-06 18:31:18 +03:00
Aarni Halinen cd2fe4e8f2 Update kaehmy dates 2019-10-06 15:34:35 +03:00
Antti Mäki bf3f1f783c Replace kaehmy_banner.png 2019-10-06 11:03:09 +00:00
Antti Mäki 37f509ff76 Replace kaehmy_banner.png 2019-10-06 11:02:01 +00:00
Aarni Halinen b58dbde83c Minor cleanup 2019-09-28 19:52:14 +03:00
Aarni Halinen 0da0a02bac Get Officials by year with query param year 2019-09-28 17:41:37 +03:00
Aarni Halinen fef74dd995 Fix brainfart 2019-09-26 21:03:26 +03:00
Aarni Halinen 2356365197 Reconstruct migrations (squash merge request) 2019-09-26 20:52:11 +03:00
Aarni Halinen 5a29c8c49d Set contact serializer 2019-09-26 20:43:13 +03:00
Aarni Halinen e365c394d8 Fix user creation receiver at official creation 2019-09-26 20:16:37 +03:00
Aarni Halinen 22371b8f15 Locales 2019-09-26 19:22:11 +03:00
Aarni Halinen 32feb46f88 Add occupation model to function as history of roles 2019-09-26 19:17:10 +03:00
Aarni Halinen 6c77fe4da6 Update autocomplete-light in requirements 2019-09-16 19:59:34 +03:00
Aarni Halinen 5dc7442d03 Update Python version and Django-autocomplete-light 2019-09-16 19:57:47 +03:00
Aarni Halinen 9f664737b6 Add Member application emails 2019-09-16 19:44:14 +03:00
Aarni Halinen 1c3b4367a2 Fix POST permissions for submit application 2019-09-16 17:55:14 +03:00
Leo Kivikunnas 9a9757c914 Moved expenses claim development to its own feature branch 2019-06-03 19:52:12 +03:00
356 changed files with 14735 additions and 10398 deletions
+6
View File
@@ -1,5 +1,11 @@
[report]
show_missing = True
omit =
*/migrations/*
*/admin.py
*/translation.py
[run]
omit =
*/migrations/*
*/admin.py
*/translation.py
-16
View File
@@ -1,16 +0,0 @@
#!/bin/bash
echo "Deploying to development."
set -e
set -x
pushd deployment
docker-compose down
docker pull "$1"
docker-compose up -d
popd
set +x
set +e
+32
View File
@@ -0,0 +1,32 @@
.DS_Store
.dockerignore
.git/
.husky/
.venv/
.vscode/
collected_static/
logs/
!logs/README
media/
!media/REMOVE_ME
misc/
node_modules/
scripts/
.coverage
.coveragerc
.env*
.eslintignore
.eslintrc.json
.gitignore
.gitlab-ci.yml
.python-version
docker-compose.yml
!manage.py
package*.json
!poetry.lock
!production_entrypoint.sh
pycodestyle.cfg
!pyproject.toml
pyright.json
README.md
stack-compose*.yml
+11
View File
@@ -0,0 +1,11 @@
DEPLOY_ENV=local
SENTRY_DSN=
HOST=api.dev.sahkoinsinoorikilta.fi
DEBUG=True
SECRET_KEY=7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp(
DB_NAME=postgres
DB_USER=postgres
DB_PASSWD=postgres
DB_HOST=db
DB_PORT=5432
EMAIL_API_KEY=
+13
View File
@@ -0,0 +1,13 @@
DEPLOY_ENV=local
#SENTRY_DSN=
HOST=localhost
DEBUG=True
SECRET_KEY=7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp(
DB_NAME=postgres
DB_USER=postgres
DB_PASSWD=postgres
DB_HOST=db
DB_PORT=5432
EMAIL_API_KEY=
GROUP_KEY=
GOOGLE_CREDS='{}'
+4 -5
View File
@@ -4,11 +4,10 @@
"jquery": true
},
"globals": {
"angular": 1,
"noty": 1,
"app": 1,
"_": 1,
"moment": 1
"angular": true,
"noty": true,
"_": true,
"moment": true
},
"extends": "eslint:recommended",
"rules": {
+12 -21
View File
@@ -1,23 +1,14 @@
*.swp
sikweb/settings.py
*~
*.pyc
*.sqlite3
uwsgi.ini
uwsgi.log
members/logs/*
.idea/
logs/
/media/
node_modules/
/.coverage
db.sqlite3
requirements_henu.txt
/collected_static/
mydatabase
settings.json
.vscode/
.DS_Store
.env
*.pyc
/collected_static/
/media/
logs/
members/logs/*
node_modules/
.coverage
.vscode/
.idea/
*.code-workspace
sik_test
venv/
venv/
.venv/
+83 -54
View File
@@ -1,102 +1,131 @@
stages:
- test
- setup
- audit
- lint
- test
- publish
- deploy
install:
image: node:16
stage: setup
script:
- npm ci
artifacts:
paths:
- node_modules
expire_in: 1 week
audit:
image: python:3.9
stage: audit
needs: []
before_script:
- pip install poetry==1.1.13
- poetry config virtualenvs.create false
- poetry install --no-interaction --no-ansi
script:
- safety check
test:
image: python:3.5
image: python:3.9
stage: test
needs: []
services:
- postgres:latest
- postgres:12
variables:
POSTGRES_DB: ci
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
DB_HOST: postgres
before_script:
- pip install poetry==1.1.13
- poetry config virtualenvs.create false
- poetry install --no-interaction --no-ansi
script:
- python -V
- pip install -r requirements.txt
- cp sikweb/settings-sample.py sikweb/default_settings.py
- cp sikweb/.ci-settings.py sikweb/settings.py
- python manage.py migrate --noinput
- python manage.py createdefaultadmin
- python manage.py test
pycodestyle:
image: python:3.5
lint:py:
image: python:3.9
stage: lint
needs: []
script:
- pip install pycodestyle
- pycodestyle --config=setup.cfg --count .
- pip install black==22.3.0
- black --check .
eslint:
image: node:alpine
lint:js:
image: node:16
stage: lint
before_script:
- npm install
needs: ["install"]
script:
- npm run eslint
- npm run lint:js
remark:
image: node:alpine
lint:md:
image: node:16
stage: lint
before_script:
- npm install
needs: ["install"]
script:
- npm run remark
- npm run lint:md
publish:
stage: publish
image: docker:stable
needs: ["test", "lint:py", "lint:js", "lint:md"]
services:
- docker:stable-dind
only:
- develop
- master
script:
- docker info
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- docker build . -t "$IMAGE_NAME"
- docker push "$IMAGE_NAME"
deploy_dev:
deploy:dev:
stage: deploy
image: alpine:latest
environment:
name: dev
url: http://web.sik.party:8000
image: docker:stable
only:
- develop
before_script:
- pwd
- apk add --update openssh
- ssh -V
- mkdir -p ~/.ssh
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
- '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
script:
- scp docker-compose.yml $DEV_SSH_USER@$DEV_SSH_HOST:~/deployment/docker-compose.yml
- scp .deploy_dev.sh $DEV_SSH_USER@$DEV_SSH_HOST:~/deployment/deploy_dev.sh
- ssh $DEV_SSH_USER@$DEV_SSH_HOST "docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY"
- ssh $DEV_SSH_USER@$DEV_SSH_HOST "bash ~/deployment/deploy_dev.sh \"$IMAGE_NAME\""
deploy_production:
stage: deploy
image: alpine:latest
environment:
name: production
url: https://sika.sahkoinsinoorikilta.fi
when: manual
name: dev
url: http://api.dev.sahkoinsinoorikilta.fi
variables:
DOCKER_HOST: $DEV_CI_DOCKER_HOST
DOCKER_TLS_VERIFY: 1
before_script:
- mkdir -p ~/.docker
- echo "$DEV_TLSCACERT" > ~/.docker/ca.pem
- echo "$DEV_TLSCERT" > ~/.docker/cert.pem
- echo "$DEV_TLSKEY" > ~/.docker/key.pem
- docker login -u gitlab-ci-token -p "$CI_BUILD_TOKEN" "$CI_REGISTRY"
script:
- docker stack deploy --with-registry-auth -c stack-compose-dev.yml "$SERVICE_NAME"
after_script:
- docker logout "$CI_REGISTRY"
deploy:production:
stage: deploy
image: docker:stable
only:
- master
environment:
name: production
url: https://api.sahkoinsinoorikilta.fi
when: manual
variables:
DOCKER_HOST: $CI_DOCKER_HOST
DOCKER_TLS_VERIFY: 1
before_script:
- pwd
- apk add --update openssh
- ssh -V
- mkdir -p ~/.ssh
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
- echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
- mkdir -p ~/.docker
- echo "$TLSCACERT" > ~/.docker/ca.pem
- echo "$TLSCERT" > ~/.docker/cert.pem
- echo "$TLSKEY" > ~/.docker/key.pem
- docker login -u gitlab-ci-token -p "$CI_BUILD_TOKEN" "$CI_REGISTRY"
script:
- ssh $PROD_SSH_USER@$PROD_SSH_HOST "zsh ~/deploy.sh"
- docker stack deploy --with-registry-auth -c stack-compose.yml "$SERVICE_NAME"
after_script:
- docker logout "$CI_REGISTRY"
+1
View File
@@ -0,0 +1 @@
_
+15
View File
@@ -0,0 +1,15 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
PURPLE='\033[0;35m'
NC='\033[0m' # No Color
. "${VIRTUAL_ENV}/bin/activate"
if [ $? -ne 0 ]
then
printf "${PURPLE}Failed to find virtualenv. Skipping pre-commit hook.\n${NC}"
exit 0
fi
npm run lint
+4 -5
View File
@@ -1,4 +1,5 @@
#!/bin/bash
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
PURPLE='\033[0;35m'
NC='\033[0m' # No Color
@@ -14,10 +15,8 @@ fi
set -e
printf "${PURPLE}Running pre-push tests.${NC}\n"
printf "${PURPLE}npm tests...${NC}\n"
npm test
printf "${PURPLE}pycodestyle tests...${NC}\n"
pycodestyle .
printf "${PURPLE}linters...${NC}\n"
npm run lint
printf "${PURPLE}unit tests...${NC}\n"
python -Wall manage.py test --noinput
set +e
+1
View File
@@ -0,0 +1 @@
16
+1 -1
View File
@@ -1 +1 @@
3.6.8
3.9
-1
View File
@@ -1 +0,0 @@
global_static
+27 -7
View File
@@ -1,8 +1,28 @@
FROM python:3.5
FROM python:3.9-slim-buster as builder
ENV PYTHONUNBUFFERED 1
ENV IS_DOCKER 1
RUN mkdir /code
WORKDIR /code
ADD requirements.txt /code/
RUN env
ADD . /code/
COPY . ./
ENV POETRY_VERSION=1.1.13
RUN pip install "poetry==$POETRY_VERSION"
RUN poetry export --without-hashes > requirements.txt
FROM python:3.9-slim-buster as server
WORKDIR /app
COPY . ./
COPY --from=builder requirements.txt ./
ENV PYTHONUNBUFFERED=1 \
# prevents python creating .pyc files
PYTHONDONTWRITEBYTECODE=1 \
# pip
PIP_NO_CACHE_DIR=off \
PIP_DISABLE_PIP_VERSION_CHECK=on \
PIP_DEFAULT_TIMEOUT=100
RUN apt-get update && apt-get install --no-install-recommends -y build-essential
RUN pip install --no-deps -r requirements.txt
RUN python manage.py collectstatic --noinput
CMD ["sh", "-c", "./production_entrypoint.sh"]
-52
View File
@@ -1,52 +0,0 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
flake8 = "*"
autopep8 = "*"
[packages]
decorator = "==4.0.9"
ipython = "==4.2.0"
pexpect = "==4.1.0"
pickleshare = "==0.7.2"
ptyprocess = "==0.5.1"
pytz = "==2016.4"
simplegeneric = "==0.8.1"
traitlets = "==4.2.1"
requests = "==2.11.1"
django-nocaptcha-recaptcha = "==0.0.19"
django-cors-headers = "==2.0.1"
djangorestframework = "==3.8.2"
djangorestframework-jwt = "==1.11.0"
coverage = "==4.3.4"
django-nose = "==1.4.5"
nose-exclude = "==0.5.0"
psycopg2-binary = "==2.7.6.1"
django-bootstrap3 = "==8.2.3"
django-tables2 = "==1.6.1"
pycodestyle = "==2.3.1"
dealer = "==2.0.5"
django-modeltranslation = "==0.13b1"
django-auditlog = "==0.4.5"
django-phonenumber-field = "==1.3.0"
django-autocomplete-light = "==3.2.10"
six = "==1.10.0"
django-suit = "==0.2.26"
telepot = "==12.3"
pyexcel = "==0.5.10"
pyexcel-xlsx = "==0.5.5"
django-import-export = "==0.7.0"
openpyxl = "==2.4.11"
django-app-namespace-template-loader = "==0.4.1"
django-filter = "==2.0.0"
"backports.shutil_get_terminal_size" = "==1.0.0"
Django = "==2.1.5"
ipython_genutils = "==0.1.0"
Pillow = "==5.4.1"
PyJWT = "==1.6.4"
[requires]
python_version = "3.6"
Generated
-706
View File
@@ -1,706 +0,0 @@
{
"_meta": {
"hash": {
"sha256": "fd7aa96e8c4fc9e3fa88b6f3b28a1101d7efd8b58e964fbece854850acf8bcea"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.6"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"aiohttp": {
"hashes": [
"sha256:00d198585474299c9c3b4f1d5de1a576cc230d562abc5e4a0e81d71a20a6ca55",
"sha256:0155af66de8c21b8dba4992aaeeabf55503caefae00067a3b1139f86d0ec50ed",
"sha256:09654a9eca62d1bd6d64aa44db2498f60a5c1e0ac4750953fdd79d5c88955e10",
"sha256:199f1d106e2b44b6dacdf6f9245493c7d716b01d0b7fbe1959318ba4dc64d1f5",
"sha256:296f30dedc9f4b9e7a301e5cc963012264112d78a1d3094cd83ef148fdf33ca1",
"sha256:368ed312550bd663ce84dc4b032a962fcb3c7cae099dbbd48663afc305e3b939",
"sha256:40d7ea570b88db017c51392349cf99b7aefaaddd19d2c78368aeb0bddde9d390",
"sha256:629102a193162e37102c50713e2e31dc9a2fe7ac5e481da83e5bb3c0cee700aa",
"sha256:6d5ec9b8948c3d957e75ea14d41e9330e1ac3fed24ec53766c780f82805140dc",
"sha256:87331d1d6810214085a50749160196391a712a13336cd02ce1c3ea3d05bcf8d5",
"sha256:9a02a04bbe581c8605ac423ba3a74999ec9d8bce7ae37977a3d38680f5780b6d",
"sha256:9c4c83f4fa1938377da32bc2d59379025ceeee8e24b89f72fcbccd8ca22dc9bf",
"sha256:9cddaff94c0135ee627213ac6ca6d05724bfe6e7a356e5e09ec57bd3249510f6",
"sha256:a25237abf327530d9561ef751eef9511ab56fd9431023ca6f4803f1994104d72",
"sha256:a5cbd7157b0e383738b8e29d6e556fde8726823dae0e348952a61742b21aeb12",
"sha256:a97a516e02b726e089cffcde2eea0d3258450389bbac48cbe89e0f0b6e7b0366",
"sha256:acc89b29b5f4e2332d65cd1b7d10c609a75b88ef8925d487a611ca788432dfa4",
"sha256:b05bd85cc99b06740aad3629c2585bda7b83bd86e080b44ba47faf905fdf1300",
"sha256:c2bec436a2b5dafe5eaeb297c03711074d46b6eb236d002c13c42f25c4a8ce9d",
"sha256:cc619d974c8c11fe84527e4b5e1c07238799a8c29ea1c1285149170524ba9303",
"sha256:d4392defd4648badaa42b3e101080ae3313e8f4787cb517efd3f5b8157eaefd6",
"sha256:e1c3c582ee11af7f63a34a46f0448fca58e59889396ffdae1f482085061a2889"
],
"version": "==3.5.4"
},
"async-timeout": {
"hashes": [
"sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f",
"sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"
],
"version": "==3.0.1"
},
"attrs": {
"hashes": [
"sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79",
"sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"
],
"version": "==19.1.0"
},
"babel": {
"hashes": [
"sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669",
"sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23"
],
"version": "==2.6.0"
},
"backports.csv": {
"hashes": [
"sha256:1277dfff73130b2e106bf3dd347adb3c5f6c4340882289d88f31240da92cbd6d",
"sha256:21f6e09bab589e6c1f877edbc40277b65e626262a86e69a70137db714eaac5ce"
],
"version": "==1.0.7"
},
"backports.shutil-get-terminal-size": {
"hashes": [
"sha256:0975ba55054c15e346944b38956a4c9cbee9009391e41b86c68990effb8c1f64",
"sha256:713e7a8228ae80341c70586d1cc0a8caa5207346927e23d09dcbcaf18eadec80"
],
"index": "pypi",
"version": "==1.0.0"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"coverage": {
"hashes": [
"sha256:01406019418aabb2d4741647cc79b0e0deb0b8c5a6f936936c303e2f82ec8e5b",
"sha256:01a07b2b9212d4da3a1294436b58ac53f1d7aa445bda666648a5357048dc7ef3",
"sha256:024682371464c6e3caa975aba12b4d5428f35613489340fce1334c74d590a057",
"sha256:07c15c4a2287116a41d5966f1f5a7be765640c2e5a1917f882850a24615db6d3",
"sha256:1d23dea598fb4d61a8577d0eb0cb2b7932db0c8d2e1394088ad5f64e3fe1febf",
"sha256:1eeb9de833c3b976ee118a8d838af437bfa596bf60a5bf0705f4370e6d181a52",
"sha256:229ab9c0d53c55d698b8784d53077bef7a5f1fb5d27e90dc7b6f91243b024513",
"sha256:2f5a8bf29bdc69976d0913745daab11f8265e46ec41153f5e1e1794088019dad",
"sha256:2f959bc1b40a3ef2c5f0c7bc282226d6d4bd585b239bcce321013afc18ff0a0f",
"sha256:36407249a0b6669c6ad4425b0f29685579df745480c03afa70f101f09f4eead3",
"sha256:3efa49e3da8f32071ee3d5d464cc6b6f8818524d4099b4a94b86a70b8c88d4f5",
"sha256:422bcc6270e1c0cd9043048ce244f49072e9bd78a2c028c2ad2cfd58c79f5936",
"sha256:4fa2b181c3bf94cfdf841148d5d9abcab1890188dd908a639bcf7a38c50092bc",
"sha256:57c0c217270e628380f4befbbf8c5312b88ba7d81fd3d1b2218a25a2608f603c",
"sha256:6ae76a6cd594107ad45525278e8addeae4628a59c8cde3999548d7fe1646465b",
"sha256:6d3c762c87062a29771015f942752caef42fcc7fe4be2b03186f96788242290c",
"sha256:8a82664931a071399d703d65af2521e2202b34f2d8db20fa22a922fec0339022",
"sha256:8b282292973a1dc4eccfcc0776e0fde75b5b3de2e35164c2d854f7dd80149e4b",
"sha256:9a7874ca91cee8714277cd6d1b52374809ab925bf6ae92607bf02509019caadb",
"sha256:9c3e6551597593c1afedcbccf1371995f94457aea82cac726d1f3a25f4507386",
"sha256:a791068e1bbe443fcd3179b1c180c27a7fc58c1554b0d10311b7659d2d2d76f5",
"sha256:adf04843188418b012dd1974e397a7ac3faa1855cbcd69083e3af4da6de9dd81",
"sha256:adfbbd4a1d22fd77b13ff992946b19873407e035504abe9ba537494fe013300f",
"sha256:b25aa3531220faaf1727fc29bc000798476b4a30f429dc07898d5da48caefa15",
"sha256:c12f34c0b50e9e8bf8c049b6c8ca59929c33cea4b1c48362c99c36838c1ee025",
"sha256:c736faa1688222a6c8a5d8be4b66ec373ad6dab27fced8ca0d2c80fed70ac6e3",
"sha256:ca36d83cd591d027952e5019149c4386e7058cd674bf8cb52dc622f768d689e9",
"sha256:e1fb21a807aa0b5cc79806d8ef09078acaa83f994e15f0f7277489ca8eda51b7",
"sha256:e53199ae110cb7e250dd5505fde34452514f4eb2f1fb7532270d2ea037454b09",
"sha256:ea9808001dcf34d368cbef430e7885fdc76a2cf8ea96a8ed8b653797dd9555bb",
"sha256:eaaefe0f6aa33de5a65f48dd0040d7fe08cac9ac6c35a56d0a7db109c3e733df",
"sha256:f27772c9ee88ed3f2a784181f3d1724561499e7e448ed1706153336baa706bd5",
"sha256:f99066d76274800145a2e658026b30962eb5079346249197e88b55c9a7855e6a",
"sha256:fd3373ccd561b79932d12a986674e642816cfc4db4507b6a22ab30c318a85429"
],
"index": "pypi",
"version": "==4.3.4"
},
"dealer": {
"hashes": [
"sha256:0a5a536fdecd9c7679534a19c59392cd21989037c14782328970a185b39e7560",
"sha256:baaac37a4c7928545cb8b0335f48abd0ea51a0274159a9a989afb8b71f8b11c3"
],
"index": "pypi",
"version": "==2.0.5"
},
"decorator": {
"hashes": [
"sha256:90022e83316363788a55352fe39cfbed357aa3a71d90e5f2803a35471de4bba8",
"sha256:f4718552326c99544a6ec602d96b7d03ef61180cf4a492c515ecb2438dd14ccc"
],
"index": "pypi",
"version": "==4.0.9"
},
"defusedxml": {
"hashes": [
"sha256:24d7f2f94f7f3cb6061acb215685e5125fbcdc40a857eff9de22518820b0a4f4",
"sha256:702a91ade2968a82beb0db1e0766a6a273f33d4616a6ce8cde475d8e09853b20"
],
"version": "==0.5.0"
},
"diff-match-patch": {
"hashes": [
"sha256:a809a996d0f09b9bbd59e9bbd0b71eed8c807922512910e05cbd3f9480712ddb"
],
"version": "==20181111"
},
"django": {
"hashes": [
"sha256:a32c22af23634e1d11425574dce756098e015a165be02e4690179889b207c7a8",
"sha256:d6393918da830530a9516bbbcbf7f1214c3d733738779f06b0f649f49cc698c3"
],
"index": "pypi",
"version": "==2.1.5"
},
"django-app-namespace-template-loader": {
"hashes": [
"sha256:356539413b5d1de0eff91aea7a03806b5ef6874ee5420ea8c273f72bbc601d74",
"sha256:7a450985479a2e07fe8a1e4e8208fc9e1d8b35503526dd28eba5f8ad4ba31d4e"
],
"index": "pypi",
"version": "==0.4.1"
},
"django-auditlog": {
"hashes": [
"sha256:70bfc673e7023d91ab8449d745425e7a4ce5eaaf2bdcbfb9b1a2209a7af60b03"
],
"index": "pypi",
"version": "==0.4.5"
},
"django-autocomplete-light": {
"hashes": [
"sha256:5ccb1c8c4b75cf72bc5dabd920190ea1ca3a340f56fb6b12d07a62202837fa75"
],
"index": "pypi",
"version": "==3.2.10"
},
"django-bootstrap3": {
"hashes": [
"sha256:6f7946d513d6340bb70f25d8ec047bf3bf37bab9e499d3baca99b1aa0ec92a52"
],
"index": "pypi",
"version": "==8.2.3"
},
"django-cors-headers": {
"hashes": [
"sha256:638aaba85f96af62557656ec559672f03d7c61769685acc405eacfaba9d4e93f",
"sha256:c766daf9eefcb9536af9817703ea29124fffee06870f9e523b75144b4d39a694"
],
"index": "pypi",
"version": "==2.0.1"
},
"django-filter": {
"hashes": [
"sha256:6f4e4bc1a11151178520567b50320e5c32f8edb552139d93ea3e30613b886f56",
"sha256:86c3925020c27d072cdae7b828aaa5d165c2032a629abbe3c3a1be1edae61c58"
],
"index": "pypi",
"version": "==2.0.0"
},
"django-import-export": {
"hashes": [
"sha256:6e748fcc647fe2a82a55136ebcbe806a45fd7fb5b1e32b33759181f1e67eeb1b",
"sha256:7e7ebeb40702eafeb2e770914c01b9961063f472b3b395eeffbea5f39efa7257"
],
"index": "pypi",
"version": "==0.7.0"
},
"django-jsonfield": {
"hashes": [
"sha256:25e53eae8bda165721773145ee0f8ae53b746bb3051b32f2821ba84d79aa77ef",
"sha256:813c52463fbbb548fe0d85bf935d0bf72e933fae2bb00ce3ba27bf69ff6fd2ad",
"sha256:cacf5a21e7c2490109a60f1122c05aa3858a8dc06952ad764831b4428164fc8e"
],
"version": "==1.1.0"
},
"django-modeltranslation": {
"hashes": [
"sha256:254ebda6caea5683407e1fb3e45ceaa2275778c6dc2db2b9d4fec3df373c2cdd"
],
"index": "pypi",
"version": "==0.13b1"
},
"django-nocaptcha-recaptcha": {
"hashes": [
"sha256:d2512d5035d5f62aba5009082db28b28995a7e6f4a46713292e0f4f350f337da"
],
"index": "pypi",
"version": "==0.0.19"
},
"django-nose": {
"hashes": [
"sha256:5df2df802c607daeeab8ac1e93abf54508ed6133eb93852310f512000124b4a5",
"sha256:87663f18cb25f01d56c84ac1ff8a0e6e6a6246264b2549b751cb239d0642e76a"
],
"index": "pypi",
"version": "==1.4.5"
},
"django-phonenumber-field": {
"hashes": [
"sha256:8db9d2dc833678b163adabd593cda7ad1dede81a1c18f67c895701fc44dc44f1"
],
"index": "pypi",
"version": "==1.3.0"
},
"django-suit": {
"hashes": [
"sha256:19ed865a478dfca81cb5f50a70317700dd70da92c465093251d0e14330a2b92b"
],
"index": "pypi",
"version": "==0.2.26"
},
"django-tables2": {
"hashes": [
"sha256:d5d3ad99580121f7ec46ea9e2420069bbd6d2f33b4fde73a376c6bf27d551146"
],
"index": "pypi",
"version": "==1.6.1"
},
"djangorestframework": {
"hashes": [
"sha256:b6714c3e4b0f8d524f193c91ecf5f5450092c2145439ac2769711f7eba89a9d9",
"sha256:c375e4f95a3a64fccac412e36fb42ba36881e52313ec021ef410b40f67cddca4"
],
"index": "pypi",
"version": "==3.8.2"
},
"djangorestframework-jwt": {
"hashes": [
"sha256:5efe33032f3a4518a300dc51a51c92145ad95fb6f4b272e5aa24701db67936a7",
"sha256:ab15dfbbe535eede8e2e53adaf52ef0cf018ee27dbfad10cbc4cbec2ab63d38c"
],
"index": "pypi",
"version": "==1.11.0"
},
"et-xmlfile": {
"hashes": [
"sha256:614d9722d572f6246302c4491846d2c393c199cfa4edc9af593437691683335b"
],
"version": "==1.0.1"
},
"idna": {
"hashes": [
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
],
"version": "==2.8"
},
"ipython": {
"hashes": [
"sha256:0480354f25b2f443e4ef1456b48f28ad1caaa6d316fca5a5eaa9ca7745ae7923",
"sha256:98452af6450e28c9c742d567d75eb6e3a7b391ad4ce8abd5679c5f85ce7fad00",
"sha256:d852fed59da67c7e45cb2192027da8bfd920a7856d295c247a45105968d24d5a",
"sha256:dba42f182b5f6f26630d2202efd30383712d9f7d8d8d9896b37ae2145deca616"
],
"index": "pypi",
"version": "==4.2.0"
},
"ipython-genutils": {
"hashes": [
"sha256:0c43fa84e93ad0e4dbecaffc6656ac1caf1a48359b2bb0a5da3af84164e3f49b",
"sha256:3a0624a251a26463c9dfa0ffa635ec51c4265380980d9a50d65611c3c2bd82a6",
"sha256:6218e9abd612fb5acfb175ea7c7b026006de4df9691d9a73c9b390cfa1a41c2b"
],
"index": "pypi",
"version": "==0.1.0"
},
"jdcal": {
"hashes": [
"sha256:948fb8d079e63b4be7a69dd5f0cd618a0a57e80753de8248fd786a8a20658a07",
"sha256:ea0a5067c5f0f50ad4c7bdc80abad3d976604f6fb026b0b3a17a9d84bb9046c9"
],
"version": "==1.4"
},
"lml": {
"hashes": [
"sha256:b1bef669dc077a1075fa64b99229b6341085b3b3a98d29c66df1853cc14e6c1a",
"sha256:ea5ba817b4adc9e9f5c21725cd2475f912933b7e2dfdf0792aed80077154f63f"
],
"version": "==0.0.9"
},
"multidict": {
"hashes": [
"sha256:024b8129695a952ebd93373e45b5d341dbb87c17ce49637b34000093f243dd4f",
"sha256:041e9442b11409be5e4fc8b6a97e4bcead758ab1e11768d1e69160bdde18acc3",
"sha256:045b4dd0e5f6121e6f314d81759abd2c257db4634260abcfe0d3f7083c4908ef",
"sha256:047c0a04e382ef8bd74b0de01407e8d8632d7d1b4db6f2561106af812a68741b",
"sha256:068167c2d7bbeebd359665ac4fff756be5ffac9cda02375b5c5a7c4777038e73",
"sha256:148ff60e0fffa2f5fad2eb25aae7bef23d8f3b8bdaf947a65cdbe84a978092bc",
"sha256:1d1c77013a259971a72ddaa83b9f42c80a93ff12df6a4723be99d858fa30bee3",
"sha256:1d48bc124a6b7a55006d97917f695effa9725d05abe8ee78fd60d6588b8344cd",
"sha256:31dfa2fc323097f8ad7acd41aa38d7c614dd1960ac6681745b6da124093dc351",
"sha256:34f82db7f80c49f38b032c5abb605c458bac997a6c3142e0d6c130be6fb2b941",
"sha256:3d5dd8e5998fb4ace04789d1d008e2bb532de501218519d70bb672c4c5a2fc5d",
"sha256:4a6ae52bd3ee41ee0f3acf4c60ceb3f44e0e3bc52ab7da1c2b2aa6703363a3d1",
"sha256:4b02a3b2a2f01d0490dd39321c74273fed0568568ea0e7ea23e02bd1fb10a10b",
"sha256:4b843f8e1dd6a3195679d9838eb4670222e8b8d01bc36c9894d6c3538316fa0a",
"sha256:5de53a28f40ef3c4fd57aeab6b590c2c663de87a5af76136ced519923d3efbb3",
"sha256:61b2b33ede821b94fa99ce0b09c9ece049c7067a33b279f343adfe35108a4ea7",
"sha256:6a3a9b0f45fd75dc05d8e93dc21b18fc1670135ec9544d1ad4acbcf6b86781d0",
"sha256:76ad8e4c69dadbb31bad17c16baee61c0d1a4a73bed2590b741b2e1a46d3edd0",
"sha256:7ba19b777dc00194d1b473180d4ca89a054dd18de27d0ee2e42a103ec9b7d014",
"sha256:7c1b7eab7a49aa96f3db1f716f0113a8a2e93c7375dd3d5d21c4941f1405c9c5",
"sha256:7fc0eee3046041387cbace9314926aa48b681202f8897f8bff3809967a049036",
"sha256:8ccd1c5fff1aa1427100ce188557fc31f1e0a383ad8ec42c559aabd4ff08802d",
"sha256:8e08dd76de80539d613654915a2f5196dbccc67448df291e69a88712ea21e24a",
"sha256:c18498c50c59263841862ea0501da9f2b3659c00db54abfbf823a80787fde8ce",
"sha256:c49db89d602c24928e68c0d510f4fcf8989d77defd01c973d6cbe27e684833b1",
"sha256:ce20044d0317649ddbb4e54dab3c1bcc7483c78c27d3f58ab3d0c7e6bc60d26a",
"sha256:d1071414dd06ca2eafa90c85a079169bfeb0e5f57fd0b45d44c092546fcd6fd9",
"sha256:d3be11ac43ab1a3e979dac80843b42226d5d3cccd3986f2e03152720a4297cd7",
"sha256:db603a1c235d110c860d5f39988ebc8218ee028f07a7cbc056ba6424372ca31b"
],
"version": "==4.5.2"
},
"nose": {
"hashes": [
"sha256:9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac",
"sha256:dadcddc0aefbf99eea214e0f1232b94f2fa9bd98fa8353711dacb112bfcbbb2a",
"sha256:f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98"
],
"version": "==1.3.7"
},
"nose-exclude": {
"hashes": [
"sha256:f78fa8b41eeb815f0486414f710f1eea0949e346cfb11d59ba6295ed69e84304"
],
"index": "pypi",
"version": "==0.5.0"
},
"odfpy": {
"hashes": [
"sha256:596021f0519623ca8717331951c95e3b8d7b21e86edc7efe8cb650a0d0f59a2b"
],
"version": "==1.4.0"
},
"openpyxl": {
"hashes": [
"sha256:626d38647c063d55803ef4971c4d43226538d4e95cb6260c094e363ee33e10c7"
],
"index": "pypi",
"version": "==2.4.11"
},
"pexpect": {
"hashes": [
"sha256:09b0a7727ce012e0fa668ef848591102d6667521655f4e72d51197c872cb9fb9",
"sha256:c381c60f1987355b65df8f08a27f428831914c8a81091bd1778ac336fa2f27e7"
],
"index": "pypi",
"version": "==4.1.0"
},
"phonenumberslite": {
"hashes": [
"sha256:2cb034b158314ca3dc034b417a64777c14b74f27f47b451270686e22eefd57d3",
"sha256:fb9212bb8f27ec4bd5ff9a109d4309deed31b45ae5a7216bacc6644759fac82d"
],
"version": "==8.10.8"
},
"pickleshare": {
"hashes": [
"sha256:92ee3b0e21632542ecc9a0a245e69a126f62e5114081bdb0d32e0edd10410033",
"sha256:b58cf7d70658a091621c0d8cc35143c8569f3827496b27ed896918c237d05d96"
],
"index": "pypi",
"version": "==0.7.2"
},
"pillow": {
"hashes": [
"sha256:051de330a06c99d6f84bcf582960487835bcae3fc99365185dc2d4f65a390c0e",
"sha256:0ae5289948c5e0a16574750021bd8be921c27d4e3527800dc9c2c1d2abc81bf7",
"sha256:0b1efce03619cdbf8bcc61cfae81fcda59249a469f31c6735ea59badd4a6f58a",
"sha256:163136e09bd1d6c6c6026b0a662976e86c58b932b964f255ff384ecc8c3cefa3",
"sha256:18e912a6ccddf28defa196bd2021fe33600cbe5da1aa2f2e2c6df15f720b73d1",
"sha256:24ec3dea52339a610d34401d2d53d0fb3c7fd08e34b20c95d2ad3973193591f1",
"sha256:267f8e4c0a1d7e36e97c6a604f5b03ef58e2b81c1becb4fccecddcb37e063cc7",
"sha256:3273a28734175feebbe4d0a4cde04d4ed20f620b9b506d26f44379d3c72304e1",
"sha256:4c678e23006798fc8b6f4cef2eaad267d53ff4c1779bd1af8725cc11b72a63f3",
"sha256:4d4bc2e6bb6861103ea4655d6b6f67af8e5336e7216e20fff3e18ffa95d7a055",
"sha256:505738076350a337c1740a31646e1de09a164c62c07db3b996abdc0f9d2e50cf",
"sha256:5233664eadfa342c639b9b9977190d64ad7aca4edc51a966394d7e08e7f38a9f",
"sha256:5d95cb9f6cced2628f3e4de7e795e98b2659dfcc7176ab4a01a8b48c2c2f488f",
"sha256:7eda4c737637af74bac4b23aa82ea6fbb19002552be85f0b89bc27e3a762d239",
"sha256:801ddaa69659b36abf4694fed5aa9f61d1ecf2daaa6c92541bbbbb775d97b9fe",
"sha256:825aa6d222ce2c2b90d34a0ea31914e141a85edefc07e17342f1d2fdf121c07c",
"sha256:9c215442ff8249d41ff58700e91ef61d74f47dfd431a50253e1a1ca9436b0697",
"sha256:a3d90022f2202bbb14da991f26ca7a30b7e4c62bf0f8bf9825603b22d7e87494",
"sha256:a631fd36a9823638fe700d9225f9698fb59d049c942d322d4c09544dc2115356",
"sha256:a6523a23a205be0fe664b6b8747a5c86d55da960d9586db039eec9f5c269c0e6",
"sha256:a756ecf9f4b9b3ed49a680a649af45a8767ad038de39e6c030919c2f443eb000",
"sha256:b117287a5bdc81f1bac891187275ec7e829e961b8032c9e5ff38b70fd036c78f",
"sha256:ba04f57d1715ca5ff74bb7f8a818bf929a204b3b3c2c2826d1e1cc3b1c13398c",
"sha256:cd878195166723f30865e05d87cbaf9421614501a4bd48792c5ed28f90fd36ca",
"sha256:cee815cc62d136e96cf76771b9d3eb58e0777ec18ea50de5cfcede8a7c429aa8",
"sha256:d1722b7aa4b40cf93ac3c80d3edd48bf93b9208241d166a14ad8e7a20ee1d4f3",
"sha256:d7c1c06246b05529f9984435fc4fa5a545ea26606e7f450bdbe00c153f5aeaad",
"sha256:e9c8066249c040efdda84793a2a669076f92a301ceabe69202446abb4c5c5ef9",
"sha256:f227d7e574d050ff3996049e086e1f18c7bd2d067ef24131e50a1d3fe5831fbc",
"sha256:fc9a12aad714af36cf3ad0275a96a733526571e52710319855628f476dcb144e"
],
"index": "pypi",
"version": "==5.4.1"
},
"psycopg2-binary": {
"hashes": [
"sha256:036bcb198a7cc4ce0fe43344f8c2c9a8155aefa411633f426c8c6ed58a6c0426",
"sha256:1d770fcc02cdf628aebac7404d56b28a7e9ebec8cfc0e63260bd54d6edfa16d4",
"sha256:1fdc6f369dcf229de6c873522d54336af598b9470ccd5300e2f58ee506f5ca13",
"sha256:21f9ddc0ff6e07f7d7b6b484eb9da2c03bc9931dd13e36796b111d631f7135a3",
"sha256:247873cda726f7956f745a3e03158b00de79c4abea8776dc2f611d5ba368d72d",
"sha256:3aa31c42f29f1da6f4fd41433ad15052d5ff045f2214002e027a321f79d64e2c",
"sha256:475f694f87dbc619010b26de7d0fc575a4accf503f2200885cc21f526bffe2ad",
"sha256:4b5e332a24bf6e2fda1f51ca2a57ae1083352293a08eeea1fa1112dc7dd542d1",
"sha256:570d521660574aca40be7b4d532dfb6f156aad7b16b5ed62d1534f64f1ef72d8",
"sha256:59072de7def0690dd13112d2bdb453e20570a97297070f876fbbb7cbc1c26b05",
"sha256:5f0b658989e918ef187f8a08db0420528126f2c7da182a7b9f8bf7f85144d4e4",
"sha256:649199c84a966917d86cdc2046e03d536763576c0b2a756059ae0b3a9656bc20",
"sha256:6645fc9b4705ae8fbf1ef7674f416f89ae1559deec810f6dd15197dfa52893da",
"sha256:6872dd54d4e398d781efe8fe2e2d7eafe4450d61b5c4898aced7610109a6df75",
"sha256:6ce34fbc251fc0d691c8d131250ba6f42fd2b28ef28558d528ba8c558cb28804",
"sha256:73920d167a0a4d1006f5f3b9a3efce6f0e5e883a99599d38206d43f27697df00",
"sha256:8a671732b87ae423e34b51139628123bc0306c2cb85c226e71b28d3d57d7e42a",
"sha256:8d517e8fda2efebca27c2018e14c90ed7dc3f04d7098b3da2912e62a1a5585fe",
"sha256:9475a008eb7279e20d400c76471843c321b46acacc7ee3de0b47233a1e3fa2cf",
"sha256:96947b8cd7b3148fb0e6549fcb31258a736595d6f2a599f8cd450e9a80a14781",
"sha256:abf229f24daa93f67ac53e2e17c8798a71a01711eb9fcdd029abba8637164338",
"sha256:b1ab012f276df584beb74f81acb63905762c25803ece647016613c3d6ad4e432",
"sha256:b22b33f6f0071fe57cb4e9158f353c88d41e739a3ec0d76f7b704539e7076427",
"sha256:b3b2d53274858e50ad2ffdd6d97ce1d014e1e530f82ec8b307edd5d4c921badf",
"sha256:bab26a729befc7b9fab9ded1bba9c51b785188b79f8a2796ba03e7e734269e2e",
"sha256:daa1a593629aa49f506eddc9d23dc7f89b35693b90e1fbcd4480182d1203ea90",
"sha256:dd111280ce40e89fd17b19c1269fd1b74a30fce9d44a550840e86edb33924eb8",
"sha256:e0b86084f1e2e78c451994410de756deba206884d6bed68d5a3d7f39ff5fea1d",
"sha256:eb86520753560a7e89639500e2a254bb6f683342af598088cb72c73edcad21e6",
"sha256:ff18c5c40a38d41811c23e2480615425c97ea81fd7e9118b8b899c512d97c737"
],
"index": "pypi",
"version": "==2.7.6.1"
},
"ptyprocess": {
"hashes": [
"sha256:0530ce63a9295bfae7bd06edc02b6aa935619f486f0f1dc0972f516265ee81a6",
"sha256:464cb76f7a7122743dd25507650db89cd447c51f38e4671602b3eaa2e38e05ae"
],
"index": "pypi",
"version": "==0.5.1"
},
"pycodestyle": {
"hashes": [
"sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766",
"sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9"
],
"index": "pypi",
"version": "==2.3.1"
},
"pyexcel": {
"hashes": [
"sha256:2a32accc28aea3994922606ecf7bef00ef058b56b1bea6af119ae3bb56468333",
"sha256:f1ffe613f09285edf42132b9afc14f81adbd0f56797e5fe05e98307d00ce175f"
],
"index": "pypi",
"version": "==0.5.10"
},
"pyexcel-io": {
"hashes": [
"sha256:55b2aa4ef81ba6e3285edfb1a3f3c3c69f9f4d52b6867318ae2381f88741143f",
"sha256:de9de0d6bf9a8906c94e3b5dbe0b3a3e0a9bc893201d6a5c5b3cf84e5119d60d"
],
"version": "==0.5.16"
},
"pyexcel-xlsx": {
"hashes": [
"sha256:488783c3f5195bed8638f6064b11d97f706641b0f065a5416297a01db6cec5ea",
"sha256:b3566162f7232336ebe0d40dd298145c18715009b020dddc210890cf6436ffb2"
],
"index": "pypi",
"version": "==0.5.5"
},
"pyjwt": {
"hashes": [
"sha256:30b1380ff43b55441283cc2b2676b755cca45693ae3097325dea01f3d110628c",
"sha256:4ee413b357d53fd3fb44704577afac88e72e878716116270d722723d65b42176"
],
"index": "pypi",
"version": "==1.6.4"
},
"python-dateutil": {
"hashes": [
"sha256:3acbef017340600e9ff8f2994d8f7afd6eacb295383f286466a6df3961e486f0",
"sha256:537bf2a8f8ce6f6862ad705cd68f9e405c0b5db014aa40fa29eab4335d4b1716",
"sha256:62a2f8df3d66f878373fd0072eacf4ee52194ba302e00082828e0d263b0418d2"
],
"version": "==2.6.0"
},
"pytz": {
"hashes": [
"sha256:8781cdd3ca70f5a536884e051797ca213b9ff479a5c1cc57240adf37cc1eff1b",
"sha256:be2ff04e94a2b5454ddcfbebb81ee8e46162734d4c2fcc90c422d16ab51f810b",
"sha256:c823de61ff40d1996fe087cec343e0503881ca641b897e0f9b86c7683a0bfee1",
"sha256:ee7c751544e35a7b7fb5e3fb25a49dade37d51e70a93e5107f10575d7102c311"
],
"index": "pypi",
"version": "==2016.4"
},
"pyyaml": {
"hashes": [
"sha256:1adecc22f88d38052fb787d959f003811ca858b799590a5eaa70e63dca50308c",
"sha256:436bc774ecf7c103814098159fbb84c2715d25980175292c648f2da143909f95",
"sha256:460a5a4248763f6f37ea225d19d5c205677d8d525f6a83357ca622ed541830c2",
"sha256:5a22a9c84653debfbf198d02fe592c176ea548cccce47553f35f466e15cf2fd4",
"sha256:7a5d3f26b89d688db27822343dfa25c599627bc92093e788956372285c6298ad",
"sha256:9372b04a02080752d9e6f990179a4ab840227c6e2ce15b95e1278456664cf2ba",
"sha256:a5dcbebee834eaddf3fa7366316b880ff4062e4bcc9787b78c7fbb4a26ff2dd1",
"sha256:aee5bab92a176e7cd034e57f46e9df9a9862a71f8f37cad167c6fc74c65f5b4e",
"sha256:c51f642898c0bacd335fc119da60baae0824f2cde95b0330b56c0553439f0673",
"sha256:c68ea4d3ba1705da1e0d85da6684ac657912679a649e8868bd850d2c299cce13",
"sha256:e23d0cc5299223dcc37885dae624f382297717e459ea24053709675a976a3e19"
],
"version": "==5.1"
},
"requests": {
"hashes": [
"sha256:545c4855cd9d7c12671444326337013766f4eea6068c3f0307fb2dc2696d580e",
"sha256:5acf980358283faba0b897c73959cecf8b841205bb4b2ad3ef545f46eae1a133"
],
"index": "pypi",
"version": "==2.11.1"
},
"simplegeneric": {
"hashes": [
"sha256:dc972e06094b9af5b855b3df4a646395e43d1c9d0d39ed345b7393560d0b9173"
],
"index": "pypi",
"version": "==0.8.1"
},
"six": {
"hashes": [
"sha256:0ff78c403d9bccf5a425a6d31a12aa6b47f1c21ca4dc2573a7e2f32a97335eb1",
"sha256:105f8d68616f8248e24bf0e9372ef04d3cc10104f1980f54d57b2ce73a5ad56a"
],
"index": "pypi",
"version": "==1.10.0"
},
"tablib": {
"hashes": [
"sha256:0f88a9cebdaa1a2cc29ae57387082ee81015d1149ecd34e48a8c8d3b4dd21670",
"sha256:5f33c079b07eb10cf9c4b4696add2ecf32c89db7729240546ecdcd5c92f67e13"
],
"version": "==0.13.0"
},
"telepot": {
"hashes": [
"sha256:8910fd6fb708e2c3ded7ca82cc945a645b717699d9f82ddff5123bb2e05f780f"
],
"index": "pypi",
"version": "==12.3"
},
"texttable": {
"hashes": [
"sha256:2b60a5304ccfbeac80ffae7350d7c2f5d7a24e9aab5036d0f82489746419d9b2"
],
"version": "==1.6.1"
},
"traitlets": {
"hashes": [
"sha256:05a66843c96a320eec09df674c16ff330a43cb07f731cf2bd88aa3645a180541",
"sha256:76eba33c89723b8fc024f950cacaf5bf2ef37999642cc9a61f4e7c1ca5cf0ac0",
"sha256:d6db3201395f9b955786d25a1817c07291e2bcb96eb7f41683ae3836836179d7"
],
"index": "pypi",
"version": "==4.2.1"
},
"urllib3": {
"hashes": [
"sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39",
"sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22"
],
"version": "==1.24.1"
},
"xlrd": {
"hashes": [
"sha256:546eb36cee8db40c3eaa46c351e67ffee6eeb5fa2650b71bc4c758a29a1b29b2",
"sha256:e551fb498759fa3a5384a94ccd4c3c02eb7c00ea424426e212ac0c57be9dfbde"
],
"version": "==1.2.0"
},
"xlwt": {
"hashes": [
"sha256:a082260524678ba48a297d922cc385f58278b8aa68741596a87de01a9c628b2e",
"sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88"
],
"version": "==1.3.0"
},
"yarl": {
"hashes": [
"sha256:024ecdc12bc02b321bc66b41327f930d1c2c543fa9a561b39861da9388ba7aa9",
"sha256:2f3010703295fbe1aec51023740871e64bb9664c789cba5a6bdf404e93f7568f",
"sha256:3890ab952d508523ef4881457c4099056546593fa05e93da84c7250516e632eb",
"sha256:3e2724eb9af5dc41648e5bb304fcf4891adc33258c6e14e2a7414ea32541e320",
"sha256:5badb97dd0abf26623a9982cd448ff12cb39b8e4c94032ccdedf22ce01a64842",
"sha256:73f447d11b530d860ca1e6b582f947688286ad16ca42256413083d13f260b7a0",
"sha256:7ab825726f2940c16d92aaec7d204cfc34ac26c0040da727cf8ba87255a33829",
"sha256:b25de84a8c20540531526dfbb0e2d2b648c13fd5dd126728c496d7c3fea33310",
"sha256:c6e341f5a6562af74ba55205dbd56d248daf1b5748ec48a0200ba227bb9e33f4",
"sha256:c9bb7c249c4432cd47e75af3864bc02d26c9594f49c82e2a28624417f0ae63b8",
"sha256:e060906c0c585565c718d1c3841747b61c5439af2211e185f6739a9412dfbde1"
],
"version": "==1.3.0"
}
},
"develop": {
"autopep8": {
"hashes": [
"sha256:33d2b5325b7e1afb4240814fe982eea3a92ebea712869bfd08b3c0393404248c"
],
"index": "pypi",
"version": "==1.4.3"
},
"entrypoints": {
"hashes": [
"sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19",
"sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"
],
"version": "==0.3"
},
"flake8": {
"hashes": [
"sha256:859996073f341f2670741b51ec1e67a01da142831aa1fdc6242dbf88dffbe661",
"sha256:a796a115208f5c03b18f332f7c11729812c8c3ded6c46319c59b53efd3819da8"
],
"index": "pypi",
"version": "==3.7.7"
},
"mccabe": {
"hashes": [
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
],
"version": "==0.6.1"
},
"pycodestyle": {
"hashes": [
"sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766",
"sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9"
],
"index": "pypi",
"version": "==2.3.1"
},
"pyflakes": {
"hashes": [
"sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0",
"sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"
],
"version": "==2.1.1"
}
}
}
+53 -34
View File
@@ -16,10 +16,6 @@ Data table app for viewing and modifying the member register, member application
Mostly static website with an event calendar and news feed.
### Coffee scale
Shows the current coffee scale status.
## Accessing the source
### Clone this repository and enter it
@@ -30,18 +26,47 @@ Set up your SSH key authentication in GitLab Profile Settings. Then clone the re
git clone git@gitlab.com:sahkoinsinoorikilta/vtmk/web2.0-backend.git
cd web2.0-backend
git checkout develop
cp scripts/git/pre-push .git/hooks/pre-push # install a script to test code before committing
```
## Windows install instructions
## Development
See [Windows install instructions](./windows_install.md)
### Poetry
## Linux/Mac install instructions
For depedencies and virtual environment, we use [poetry](https://python-poetry.org/).
See [Linux/Mac install instructions](./linux_install.md)
```bash
python3 -m pip install poetry
```
## Initializing data
The easiest integration with VSCode is to have poetry install virtual environment in project folder, configured with CMD
```bash
python3 -m poetry config virtualenvs.in-project true
```
Start developing by install dependencies first
#### CMDs
Activate virtual environment in shell
```bash
python3 -m poetry shell
```
Install dependencies
```bash
poetry install
```
### npm scripts
We use Node.js for few development tasks, like linting. Easiest way to install Node is [nvm](https://github.com/nvm-sh/nvm).
TODO: List scripts
### Initializing data
Run the following `manage.py` commands. Do not run these in production without thinking!
@@ -51,33 +76,23 @@ python manage.py initialize # creates user groups
python manage.py createdummydata # creates dummy members to the member register
```
## Running
### Running
### Use runserver command
```bash
python manage.py runserver
```
#### Visit the page
Visit [https://localhost:8000](https://localhost:8000) in your browser!
Using address `0.0.0.0` will bind to all IP addresses. Using `localhost` will only bind to your machine.
```bash
python manage.py runserver 0.0.0.0:8000
```
Using address `0.0.0.0` will bind to all IP addresses. Using `localhost` will only bind to your machine.
### Visit the page
Visit [https://localhost:8000](https://localhost:8000) in your browser!
## Running in production
Run the project in production with gunicorn. Refer to [this page](https://www.digitalocean.com/community/tutorials/how-to-set-up-django-with-postgres-nginx-and-gunicorn-on-ubuntu-16-04) for instructions.
Install production dependencies.
```bash
pip install -r requirements.production.txt
```
## Development workflow
Do not use `rebase` when pulling or merging changes. Rebasing transforms the commit history and makes it appear more linear. This is a plus e.g. when working with a few people, but in this project traditional merging is recommended for clarity.
### Development workflow
When you start working on a feature, create a feature branch for your changes. These feature branches should be prefixed with `feature`.
@@ -98,7 +113,7 @@ Merge requests to `master` should be reviewed by multiple developers. Only a mod
Lint python files using `pycodestyle` with
```bash
pycodestyle --config=setup.cfg --count .
pycodestyle --config=pycodestyle.cfg --count .
```
Lint javascript and markdown using `eslint` and `remark` with
@@ -114,13 +129,17 @@ Use an editor with linting capabilities to write pretty code that passes linting
Run unit tests with
```bash
python manage.py test -v 2
python manage.py test
```
Due to the mostly static nature of the project, most elements are difficult to properly unit test. If you write code with actual logic, make sure to write at least one unit or integration test that tests your code's core functionality.
Tests are located in `tests.py` under every subproject.
### GitLab CI
## Production
Project is run in production with Docker. See `Dockerfile` for details.
## GitLab CI
All pushed changes go through the GitLab Continuous Integration, which consists of automated unit testing and linting. Make sure your changes pass both before merging to `develop` or `master`.
-3
View File
@@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.
View File
-3
View File
@@ -1,3 +0,0 @@
from django.db import models
# Create your models here.
@@ -1,124 +0,0 @@
body {
background-color: white;
font-family: monospace;
color: black;
}
#container{
position:relative;
width:95%;
margin-left:auto;
margin-right:auto;
height:100%;
overflow:hidden;
}
#upper{
background-size: contain;
background-repeat: no-repeat;
background-position: bottom center;
background-image: url("/static/coffee_scale/img/smokes.png");
transform-origin: bottom;
animation: smokes 8s ease-in-out 0s infinite;
opacity:0;
height:40%;
}
#lower{
position:relative;
background-size: contain;
background-repeat: no-repeat;
background-position: top center;
background-image: url("/static/coffee_scale/img/coffeecup3.png");
height:60%;
}
#scale{
position:absolute;
top:80%;
width:90%;
height:10%;
margin: 0% 5% 0% 5%;
background: lightgrey;
border-radius: 10px;
overflow:hidden;
}
#scale2{
width: 0%;
transition: width 2s;
height:100%;
background: green;
border-radius: 10px;
}
.brewtime{
text-align:right;
position:absolute;
right:0px;
z-index:5;
font-size:10vw;
}
#address{
text-align:left;
position:absolute;
left:0px;
z-index:5;
font-size:4vw;
color: #333;
}
.layertwo{
display: None;
}
noscript{
color:red;
}
.text{
color:green;
position:absolute;
top:50%;
left:50%;
}
.brewing{
animation: brewing 5s ease-in-out 0s infinite;
}
.hurry{
color:red !important;
}
.unknown{
color:orange !important;
animation: unknown 5s ease-in-out 0s infinite;
}
.friday{
animation: friday 20s ease-in-out 0s infinite;
}
.normal{
animation: normal 1000s ease-in-out 0s infinite;
}
.coffeeready{
animation: coffeeready 10s ease-in-out 0s;
}
@keyframes smokes {
0% {transform: skewX(-10deg);}
50% {transform: skewX(10deg);}
100% {transform: skewX(-10deg);}
}
@keyframes brewing {
0% {color:green;}
50% {color: transparent;}
100% {color:green;}
}
@keyframes coffeeready {
0% {background-color:white;}
25% {background-color:rgb(100, 255, 100);}
50% {background-color:white;}
75% {background-color:rgb(100, 255, 100);}
100% {background-color:white;}
}
@keyframes unknown {
0%,40% {transform: rotate(0deg);}
60%,100% {transform: rotate(360deg);}
}
@keyframes friday {
0% {transform: rotate(0deg);}
100% {transform: rotate(360deg);}
}
@keyframes normal {
0%,49% {transform: rotate(0deg);}
50%,100% {transform: rotate(360deg);}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

@@ -1,183 +0,0 @@
//Inner state
var lastBrew = new Date(0);
var brewing = false;
var backoff = 2000;
//MQTT client config
var username = "coffee-user-"+ Math.random();
// eslint-disable-next-line no-undef
var client = new Paho.MQTT.Client("sika.sahkoinsinoorikilta.fi", 9001, username);
client.onMessageArrived = function (message) {
// eslint-disable-next-line no-console
console.log("Topic: "+message.destinationName+" msg: "+message.payloadString);
var ev = new CustomEvent(message.destinationName, {'detail': message.payloadString});
window.dispatchEvent(ev);
}
function reconnect(responseObject){
if (responseObject.errorCode !== 0) {
console.log("connection lost! Reason: "+responseObject.errorMessage); // eslint-disable-line no-console
setTimeout(function(){
client.connect({onSuccess:onConnect, useSSL:true, onFailure: reconnect});
}, backoff);
}
}
function onConnect() {
console.log("MQTT connected"); // eslint-disable-line no-console
//set and reset reconnector
client.onConnectionLost = reconnect
// subscribe to topics
client.subscribe("sik/kiltahuone/kahvivaaka/cups");
client.subscribe("sik/kiltahuone/kahvivaaka/brewing");
client.subscribe("sik/kiltahuone/kahvivaaka/brewtime");
}
// data update and parse functions
function parseCups(ev){
var cups = parseFloat(ev.detail).toFixed(1)
function makeEvent(cups) {
return (String(cups) !== '-1.0')
? new CustomEvent("cupsChanged", {'detail': cups})
: new CustomEvent("cupsError", {'detail': 'Error: unable to fetch cups :('});
}
window.dispatchEvent(makeEvent(cups));
}
function updateCups(ev){
$("#text").text(ev.detail);
}
function showCupsError(ev) {
$('#text').text(ev.detail);
$('#text').css({
'font-size': '7vh',
'left': '0',
'top': '40%',
'width': '100%',
'text-align': 'center',
'color': 'red',
});
$('#lower').css({'background-image': 'none'});
}
function updateScale(ev){
$("#scale2").css({width: Math.min(ev.detail/9*100,100) + '%'});
}
function tick(){
var ev = new CustomEvent("tick", {'detail': new Date()});
window.dispatchEvent(ev);
}
function updateTime(ev){
var now = ev.detail;
$("#time").html(formatTime(now.getHours(),now.getMinutes(),now.getSeconds()));
}
function coffeeLowEffect(ev){
ev.detail <= 2 ? $("#text").addClass("hurry") : $("#text").removeClass("hurry");
}
function coffeeReadyEffect(){
$("body").addClass("coffeeready");
// autoclear animation class in 10s
setTimeout(function(){$("body").removeClass("coffeeready");}, 10000);
}
function hotEffect(ev){
var opa = Math.max(100 - ev.detail / 90000,0);
$("#upper").css({opacity: opa/100});
}
function brewAnimStart(){
$(".text").addClass("brewing");
$(".layerone").hide();
$(".layertwo").show();
}
function brewAnimEnd(){
$(".text").removeClass("brewing");
$(".layertwo").hide();
$(".layerone").show();
}
function brewNotifier(ev){
var new_brewing = parseInt(ev.detail);
if (new_brewing == 1 && brewing == 0){
window.dispatchEvent(new Event("brewStart"));
} else if (new_brewing == 0 && brewing == 1){
window.dispatchEvent(new Event("brewEnd"));
}
brewing = new_brewing;
}
function brewTimeParser(ev){
lastBrew = new Date(parseInt(ev.detail)*1000.0);
}
function updateBrewDiff(){
var now = new Date();
var timeDiff = Math.max(now.getTime() - lastBrew.getTime(), 0);
var eve = new CustomEvent("dtUpdate", {'detail': timeDiff});
window.dispatchEvent(eve);
}
function updateBrewTime(ev){
var timeDiff = ev.detail;
var timeStr;
if (timeDiff < 3600000){
timeStr = Math.round(timeDiff / 60000) + ' min'
} else if (timeDiff < 10000* 3600 * 1000){ // 1000h
timeStr = '~' + Math.round(timeDiff / 3600000 * 2) / 2 + ' h';
} else {
timeStr = "???"
}
$("#brewtime").html(timeStr);
}
// Helpers
function nToS(num){
return num < 10 ? "0" + num : "" + num;
}
function formatTime(hours, minutes, seconds){
return nToS(hours)+":"+nToS(minutes)+":"+nToS(seconds)
}
function resize(){
var w = $("#container").width();
var h = $("#container").height();
var s = w > h ? h : w;
var font = s * 0.8 * 0.38/Math.sqrt(3);
$(".text").css({ top: s*0.16-font/2 + 'px',
fontSize: font + 'px',
marginLeft: -font*3*3/10 + 'px'});
}
// Init everything
$(document).ready(function(){
client.connect({onSuccess:onConnect, useSSL:true, onFailure:reconnect});
//connect MQTT event listeners
window.addEventListener("sik/kiltahuone/kahvivaaka/cups", parseCups);
window.addEventListener("sik/kiltahuone/kahvivaaka/brewing", brewNotifier);
window.addEventListener("sik/kiltahuone/kahvivaaka/brewtime", brewTimeParser);
//connect other event listeners
window.addEventListener("cupsChanged", updateCups);
window.addEventListener("cupsChanged", coffeeLowEffect);
window.addEventListener("cupsChanged", updateScale);
window.addEventListener("cupsChanged", resize);
window.addEventListener("cupsError", showCupsError);
window.addEventListener("cupsError", coffeeLowEffect);
window.addEventListener("cupsError", updateScale);
window.addEventListener("brewStart", brewAnimStart);
window.addEventListener("brewEnd", brewAnimEnd);
window.addEventListener("brewEnd", coffeeReadyEffect);
window.addEventListener("tick", updateTime);
window.addEventListener("tick", updateBrewDiff);
window.addEventListener("dtUpdate", updateBrewTime);
window.addEventListener("dtUpdate", hotEffect);
//start time based events
setInterval(tick, 100);
tick();
});
$(window).resize(resize);
-41
View File
@@ -1,41 +0,0 @@
{% load i18n %}
{% load static %}
<html>
<head>
<title>Coffee Cups @Guild Room - AYY SIK ry</title>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="3600">
<meta http-equiv="cache-control" content="max-age=0" />
<meta http-equiv="cache-control" content="no-cache" />
<meta http-equiv="expires" content="0" />
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
<meta http-equiv="pragma" content="no-cache" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.2/mqttws31.js"
type="text/javascript"></script>
<link rel="stylesheet" href="{% static "coffee_scale/css/coffee.css" %}">
<script src="{% static "coffee_scale/js/coffee.js" %}"></script>
</head>
<body>
<div id="container">
<span id="brewtime" class="brewtime layerone"></span>
<span class="brewtime layertwo">:)</span>
<span id="address">
ka.dy.fi
<noscript><br>This page uses JavaScript!</noscript>
<br><span id="time"></span></span>
</span>
<div id="upper">
</div>
<!--Kahvinkeitin on rikki. Varakeittimellä keitettyä kahvia saattaa olla.-->
<div id="lower" class="normal">
<div id="text" class="text layerone">???</div>
<div class="text layertwo">&nbsp;+</div>
<div id="scale"><div id="scale2"></div></div>
</div>
</div>
</body>
</html>
-2
View File
@@ -1,2 +0,0 @@
from django.test import TestCase, Client
from django.conf import settings
-12
View File
@@ -1,12 +0,0 @@
from django.conf.urls import url
from django.conf import settings
from .views import coffee_view
urlpatterns = [
# landing page
url(r'^$', coffee_view),
]
if settings.DEBUG:
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
urlpatterns += staticfiles_urlpatterns()
-11
View File
@@ -1,11 +0,0 @@
from django.shortcuts import render
from django.http import JsonResponse
from django.utils import timezone
import logging
from django.conf import settings
def coffee_view(request):
return render(request, 'coffee_scale:coffee.html')
+13 -2
View File
@@ -2,12 +2,23 @@ version: '3'
services:
db:
image: postgres
image: postgres:12
volumes:
- dbdata:/var/lib/postgresql/data
ports:
- "5432:5432"
environment:
- POSTGRES_PASSWORD=postgres
web:
build: .
image: registry.gitlab.com/sahkoinsinoorikilta/vtmk/web2.0-backend
command: ["bash", "-c", "cd /code && ./wait-for-it.sh db:5432 -- bash setup.sh --no-input --no-npm && python manage.py runserver 0.0.0.0:8000"]
env_file:
- .env
ports:
- "8000:8000"
depends_on:
- db
volumes:
dbdata:
View File
-3
View File
@@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.
-5
View File
@@ -1,5 +0,0 @@
from django.apps import AppConfig
class ExpensesClaimConfig(AppConfig):
name = 'expenses_claim'
-30
View File
@@ -1,30 +0,0 @@
"""Expenses claim form."""
from django import forms
from string import ascii_uppercase
class ExpensesClaim(forms.Form):
"""Expenses claim form"""
name = forms.CharField(label='Nimi', max_length=100)
iban = forms.CharField(label='IBAN', max_length=100)
amount = forms.DecimalField(label="Summa", decimal_places=2)
def clean_iban(self):
"""Validate IBAN."""
data = self.cleaned_data['iban']
# Remove spaces.
data = data.replace(" ", "")
# Move first 4 symbols to the end of the string.
data = data[4:] + data[0:4]
LETTERS = {letter: str(index) for index,
letter in enumerate(ascii_uppercase, start=10)}
data = data.upper()
# Replace all letters with numbers, so that A=10, B=11, ..., Z=35.
data = [LETTERS[char] if char in LETTERS else char for char in data]
data = ''.join(data)
# If data modulo 97 != 1 the IBAN number is invalid.
if int(data) % 97 != 1:
raise forms.ValidationError("Invalid IBAN number!")
return data
-3
View File
@@ -1,3 +0,0 @@
from django.db import models
# Create your models here.
-7
View File
@@ -1,7 +0,0 @@
Dis is claim
<form action="" method="post">
{{ form }}
<input type="submit" value="Submit">
{% csrf_token %}
</form>
-54
View File
@@ -1,54 +0,0 @@
from django.test import TestCase
from .forms import ExpensesClaim
class ExpensesClaimTest(TestCase):
"""Test expenses claim form."""
def test_valid_data1(self):
form = ExpensesClaim({
'name': "John Doe",
'iban': "FI37 1590 3000 0007 76",
'amount': 12.54
})
self.assertTrue(form.is_valid())
def test_valid_data2(self):
form = ExpensesClaim({
'name': "John Cena",
'iban': "AL35202111090000000001234567",
'amount': 12
})
self.assertTrue(form.is_valid())
def test_valid_data3(self):
form = ExpensesClaim({
'name': "John Wayne",
'iban': "BR1500000000000010932840814P2",
'amount': 12.0
})
self.assertTrue(form.is_valid())
def test_invalid_iban(self):
form = ExpensesClaim({
'name': "John Lennon",
'iban': "FI3734 1590 3000 0007 76",
'amount': 12.54
})
self.assertFalse(form.is_valid())
def test_invalid_amount(self):
form = ExpensesClaim({
'name': "John Kenedy",
'iban': "FI37 1590 3000 0007 76",
'amount': "asd"
})
self.assertFalse(form.is_valid())
def test_invalid_amount_decimal_places(self):
form = ExpensesClaim({
'name': "John Travolta",
'iban': "FI37 1590 3000 0007 76",
'amount': 12.544
})
self.assertFalse(form.is_valid())
-9
View File
@@ -1,9 +0,0 @@
"""Expenses claim urls."""
from django.conf.urls import url
from .views import claim
urlpatterns = [
url(r'^new', claim)
]
-22
View File
@@ -1,22 +0,0 @@
"""Expenses claim views."""
from django.shortcuts import render
from django.views.decorators.http import require_http_methods
from django.http import HttpResponse
from .forms import ExpensesClaim
# Allow only GET or POST
@require_http_methods(["GET", "POST"])
def claim(request):
"""Render expenses claim form."""
if request.method == 'POST':
form = ExpensesClaim(request.POST)
if form.is_valid():
return HttpResponse()
elif request.method == 'GET':
form = ExpensesClaim()
return render(request, 'claim.html', {'form': form})
+9 -2
View File
@@ -2,8 +2,15 @@
from django.contrib import admin
from infoscreen.models import (
Rotation, InfoItem, InfoInstance, ImageInfoItem,
ExternalImageInfoItem, ABBInfoItem, ExternalWebsiteInfoItem, VideoInfoItem)
Rotation,
InfoItem,
InfoInstance,
ImageInfoItem,
ExternalImageInfoItem,
ABBInfoItem,
ExternalWebsiteInfoItem,
VideoInfoItem,
)
# Register your models here.
admin.site.register(Rotation)
+1 -1
View File
@@ -6,4 +6,4 @@ from django.apps import AppConfig
class InfoscreenConfig(AppConfig):
"""Infoscreen app configuration."""
name = 'infoscreen'
name = "infoscreen"
+128 -36
View File
@@ -11,81 +11,173 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
("contenttypes", "0002_remove_content_type_name"),
]
operations = [
migrations.CreateModel(
name='HSLDataModel',
name="HSLDataModel",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('data', models.TextField(default='', editable=False)),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("data", models.TextField(default="", editable=False)),
],
),
migrations.CreateModel(
name='InfoInstance',
name="InfoInstance",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('duration', models.FloatField(default=15.0)),
('item_id', models.PositiveIntegerField()),
('item_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("duration", models.FloatField(default=15.0)),
("item_id", models.PositiveIntegerField()),
(
"item_type",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="contenttypes.ContentType",
),
),
],
),
migrations.CreateModel(
name='InfoItem',
name="InfoItem",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('expire_date', models.DateTimeField(blank=True, null=True)),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255)),
("expire_date", models.DateTimeField(blank=True, null=True)),
],
),
migrations.CreateModel(
name='Rotation',
name="Rotation",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255)),
],
),
migrations.CreateModel(
name='ABBInfoItem',
name="ABBInfoItem",
fields=[
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
(
"infoitem_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="infoscreen.InfoItem",
),
),
],
bases=('infoscreen.infoitem',),
bases=("infoscreen.infoitem",),
),
migrations.CreateModel(
name='ExternalImageInfoItem',
name="ExternalImageInfoItem",
fields=[
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
('url', models.TextField()),
(
"infoitem_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="infoscreen.InfoItem",
),
),
("url", models.TextField()),
],
bases=('infoscreen.infoitem',),
bases=("infoscreen.infoitem",),
),
migrations.CreateModel(
name='HslInfoItem',
name="HslInfoItem",
fields=[
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
(
"infoitem_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="infoscreen.InfoItem",
),
),
],
bases=('infoscreen.infoitem',),
bases=("infoscreen.infoitem",),
),
migrations.CreateModel(
name='ImageInfoItem',
name="ImageInfoItem",
fields=[
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
('img', models.ImageField(upload_to='infoimages/')),
(
"infoitem_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="infoscreen.InfoItem",
),
),
("img", models.ImageField(upload_to="infoimages/")),
],
bases=('infoscreen.infoitem',),
bases=("infoscreen.infoitem",),
),
migrations.CreateModel(
name='SossoInfoItem',
name="SossoInfoItem",
fields=[
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
(
"infoitem_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="infoscreen.InfoItem",
),
),
],
bases=('infoscreen.infoitem',),
bases=("infoscreen.infoitem",),
),
migrations.AddField(
model_name='infoinstance',
name='rotation',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='instances', to='infoscreen.Rotation'),
model_name="infoinstance",
name="rotation",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="instances",
to="infoscreen.Rotation",
),
),
]
+14 -4
View File
@@ -9,15 +9,25 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('infoscreen', '0001_initial'),
("infoscreen", "0001_initial"),
]
operations = [
migrations.CreateModel(
name='CoffeeInfoItem',
name="CoffeeInfoItem",
fields=[
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
(
"infoitem_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="infoscreen.InfoItem",
),
),
],
bases=('infoscreen.infoitem',),
bases=("infoscreen.infoitem",),
),
]
@@ -9,33 +9,63 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('infoscreen', '0002_coffeeinfoitem'),
("infoscreen", "0002_coffeeinfoitem"),
]
operations = [
migrations.CreateModel(
name='ApyInfoItem',
name="ApyInfoItem",
fields=[
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
(
"infoitem_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="infoscreen.InfoItem",
),
),
],
bases=('infoscreen.infoitem',),
bases=("infoscreen.infoitem",),
),
migrations.CreateModel(
name='EventInfoItem',
name="EventInfoItem",
fields=[
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
(
"infoitem_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="infoscreen.InfoItem",
),
),
],
bases=('infoscreen.infoitem',),
bases=("infoscreen.infoitem",),
),
migrations.CreateModel(
name='ExternalWebsiteInfoItem',
name="ExternalWebsiteInfoItem",
fields=[
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
('url', models.TextField()),
(
"infoitem_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="infoscreen.InfoItem",
),
),
("url", models.TextField()),
],
bases=('infoscreen.infoitem',),
bases=("infoscreen.infoitem",),
),
migrations.DeleteModel(
name='CoffeeInfoItem',
name="CoffeeInfoItem",
),
]
+15 -5
View File
@@ -9,16 +9,26 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('infoscreen', '0003_auto_20170329_1857'),
("infoscreen", "0003_auto_20170329_1857"),
]
operations = [
migrations.CreateModel(
name='VideoInfoItem',
name="VideoInfoItem",
fields=[
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
('video', models.FileField(upload_to='infovideos/')),
(
"infoitem_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="infoscreen.InfoItem",
),
),
("video", models.FileField(upload_to="infovideos/")),
],
bases=('infoscreen.infoitem',),
bases=("infoscreen.infoitem",),
),
]
@@ -8,18 +8,18 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('infoscreen', '0004_videoinfoitem'),
("infoscreen", "0004_videoinfoitem"),
]
operations = [
migrations.AlterField(
model_name='externalimageinfoitem',
name='url',
model_name="externalimageinfoitem",
name="url",
field=models.URLField(),
),
migrations.AlterField(
model_name='externalwebsiteinfoitem',
name='url',
model_name="externalwebsiteinfoitem",
name="url",
field=models.URLField(),
),
]
@@ -8,14 +8,14 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('infoscreen', '0005_auto_20170913_1841'),
("infoscreen", "0005_auto_20170913_1841"),
]
operations = [
migrations.DeleteModel(
name='HSLDataModel',
name="HSLDataModel",
),
migrations.DeleteModel(
name='HslInfoItem',
name="HslInfoItem",
),
]
+14 -4
View File
@@ -7,15 +7,25 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('infoscreen', '0006_delete_hsldatamodel'),
("infoscreen", "0006_delete_hsldatamodel"),
]
operations = [
migrations.CreateModel(
name='LunchItem',
name="LunchItem",
fields=[
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
(
"infoitem_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="infoscreen.InfoItem",
),
),
],
bases=('infoscreen.infoitem',),
bases=("infoscreen.infoitem",),
),
]
@@ -0,0 +1,28 @@
# Generated by Django 3.2.14 on 2022-08-01 19:13
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("infoscreen", "0007_lunchitem"),
]
operations = [
migrations.AlterField(
model_name="infoinstance",
name="id",
field=models.AutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name="infoitem",
name="id",
field=models.AutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name="rotation",
name="id",
field=models.AutoField(primary_key=True, serialize=False),
),
]
+51 -53
View File
@@ -16,6 +16,7 @@ class InfoItem(models.Model):
class __meta__:
abstract = True
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255)
# expire_date = None means never expiring item
expire_date = models.DateTimeField(blank=True, null=True)
@@ -23,14 +24,14 @@ class InfoItem(models.Model):
def get_template_url(self):
"""Get infoscreen template url."""
raise NotImplementedError(
"inheriting classes must implement get_template_url")
raise NotImplementedError("inheriting classes must implement get_template_url")
@staticmethod
def get_create_template_url():
"""Get create infoscreen template url command."""
raise NotImplementedError(
"inheriting classes must implement get_create_template_url")
"inheriting classes must implement get_create_template_url"
)
@classmethod
def create_from_dict(cls, d):
@@ -42,14 +43,13 @@ class InfoItem(models.Model):
def update_from_dict(self, d):
"""Update model based on given dict."""
try:
expire_date = d.pop('expire_date', None)
self.expire_date = datetime.strptime(
expire_date, "%Y-%m-%d %H:%M:%S")
expire_date = d.pop("expire_date", None)
self.expire_date = datetime.strptime(expire_date, "%Y-%m-%d %H:%M:%S")
except:
pass
dmap = {
'name': 'name',
"name": "name",
}
for k, v in d.items():
try:
@@ -61,13 +61,13 @@ class InfoItem(models.Model):
def get_dict(self):
"""Convert django model to dict and return it."""
return {
'id': self.id,
'name': self.name,
'item_type': ContentType.objects.get_for_model(self).id,
'template_url': self.get_template_url(),
'display_name': self.display_name,
'create_template_url': self.get_create_template_url(),
'options': {}
"id": self.id,
"name": self.name,
"item_type": ContentType.objects.get_for_model(self).id,
"template_url": self.get_template_url(),
"display_name": self.display_name,
"create_template_url": self.get_create_template_url(),
"options": {},
}
def delete(self):
@@ -75,8 +75,8 @@ class InfoItem(models.Model):
# since generic foreign keys suck, delete info
# items pointing here manually
InfoInstance.objects.filter(
item_id=self.id,
item_type=ContentType.objects.get_for_model(self)).delete()
item_id=self.id, item_type=ContentType.objects.get_for_model(self)
).delete()
super().delete()
@classmethod
@@ -139,7 +139,7 @@ class ExternalWebsiteInfoItem(InfoItem):
def get_dict(self):
"""Convert django model to dict and return it."""
d = super().get_dict()
d["options"] = {'url': self.url}
d["options"] = {"url": self.url}
return d
@classmethod
@@ -152,23 +152,22 @@ class ExternalWebsiteInfoItem(InfoItem):
def get_list(self):
"""Return list containing infoitem data."""
return {
'id': self.id,
'name': self.name,
'url': self.url,
"id": self.id,
"name": self.name,
"url": self.url,
}
def update_from_dict(self, d):
"""Update model based on given dict."""
try:
expire_date = d.pop('expire_date', None)
self.expire_date = datetime.strptime(
expire_date, "%Y-%m-%d %H:%M:%S")
expire_date = d.pop("expire_date", None)
self.expire_date = datetime.strptime(expire_date, "%Y-%m-%d %H:%M:%S")
except:
pass
dmap = {
'name': 'name',
'url': 'url',
"name": "name",
"url": "url",
}
for k, v in d.items():
try:
@@ -241,14 +240,14 @@ class ImageInfoItem(InfoItem):
def get_dict(self):
"""Convert django model to dict and return it."""
d = super().get_dict()
d["options"] = {'img': self.img.url}
d["options"] = {"img": self.img.url}
return d
class VideoInfoItem(InfoItem):
"""Class for Video Infoscreen item."""
display_name = ("Video")
display_name = "Video"
video = models.FileField(upload_to="infovideos/")
def get_template_url(self):
@@ -263,7 +262,7 @@ class VideoInfoItem(InfoItem):
def get_dict(self):
"""Convert django model to dict and return it."""
d = super().get_dict()
d["options"] = {'video': self.video.url}
d["options"] = {"video": self.video.url}
return d
@@ -285,7 +284,7 @@ class ExternalImageInfoItem(InfoItem):
def get_dict(self):
"""Convert django model to dict and return it."""
d = super().get_dict()
d["options"] = {'img': self.url}
d["options"] = {"img": self.url}
return d
@classmethod
@@ -298,15 +297,14 @@ class ExternalImageInfoItem(InfoItem):
def update_from_dict(self, d):
"""Update model based on given dict."""
try:
expire_date = d.pop('expire_date', None)
self.expire_date = datetime.strptime(
expire_date, "%Y-%m-%d %H:%M:%S")
expire_date = d.pop("expire_date", None)
self.expire_date = datetime.strptime(expire_date, "%Y-%m-%d %H:%M:%S")
except:
pass
dmap = {
'name': 'name',
'url': 'url',
"name": "name",
"url": "url",
}
for k, v in d.items():
try:
@@ -319,12 +317,15 @@ class ExternalImageInfoItem(InfoItem):
class InfoInstance(models.Model):
"""Class for Info instance in Infoscreen."""
rotation = models.ForeignKey('Rotation', related_name='instances', on_delete=models.CASCADE)
id = models.AutoField(primary_key=True)
rotation = models.ForeignKey(
"Rotation", related_name="instances", on_delete=models.CASCADE
)
duration = models.FloatField(default=15.0) # seconds
# generic relation to some kind of InfoItem
item_id = models.PositiveIntegerField()
item_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
item = GenericForeignKey('item_type', 'item_id')
item = GenericForeignKey("item_type", "item_id")
@classmethod
def create_from_dict(cls, d):
@@ -337,31 +338,27 @@ class InfoInstance(models.Model):
except:
raise RuntimeError("invalid parameters supplied supplied")
try:
return cls.objects.create(
rotation=rotation,
item=item,
duration=duration
)
return cls.objects.create(rotation=rotation, item=item, duration=duration)
except:
raise RuntimeError("error while adding instance to db")
def get_dict(self):
"""Convert django model to dict and return it."""
return {
'id': self.id,
'item': self.item.get_dict(),
'duration': self.duration,
"id": self.id,
"item": self.item.get_dict(),
"duration": self.duration,
}
def __str__(self):
"""Return model name."""
return "{}: {} ({}s)".format(
self.rotation.name, self.item.name, self.duration)
return "{}: {} ({}s)".format(self.rotation.name, self.item.name, self.duration)
class Rotation(models.Model):
"""Class for rotation model."""
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255)
def get_dict(self):
@@ -370,21 +367,20 @@ class Rotation(models.Model):
# to avoid excluding items with no expire_date)
now = timezone.now()
instances = self.instances.all()
filtered = filter(lambda i: (i.item.expire_date or now) >= now,
list(instances))
filtered = filter(lambda i: (i.item.expire_date or now) >= now, list(instances))
instance_list = list(map(lambda i: i.get_dict(), filtered))
return {
'id': self.id,
'name': self.name,
'instances': instance_list,
"id": self.id,
"name": self.name,
"instances": instance_list,
}
def get_list(self):
"""Return list containing infoitem data."""
return {
'id': self.id,
'name': self.name,
"id": self.id,
"name": self.name,
}
def __str__(self):
@@ -395,6 +391,7 @@ class Rotation(models.Model):
class ImageUploadForm(forms.Form):
"""Form used to handle imageuploads to infoscreen app."""
id = models.AutoField(primary_key=True)
name = forms.CharField()
image = forms.ImageField()
@@ -402,5 +399,6 @@ class ImageUploadForm(forms.Form):
class UploadFileForm(forms.Form):
"""Form used for uploading file."""
id = models.AutoField(primary_key=True)
name = forms.CharField()
video = forms.FileField()
+1 -1
View File
@@ -3,7 +3,7 @@ body {
}
#header:after {
content: " ";
content: " ";
display: block;
clear: both;
}
+2 -2
View File
@@ -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. */
}
+3 -3
View File
@@ -31,10 +31,10 @@
max-height 200px;
}
#sossoimage {
#sossoimage {
height:300px;
position: relative;
left: 0px;
position: relative;
left: 0px;
top: 0px;
}
@@ -1,5 +1,5 @@
<link rel="stylesheet" href="/static/infoscreen/css/events.css">
<link href="https://fonts.googleapis.com/css?family=Droid+Sans+Mono" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Droid+Sans+Mono" rel="stylesheet">
<div class="container" ng-app="myApp" ng-controller="EventController">
<div class="header-row row">
<div class="col-sm-6">Tapahtuma</div>
@@ -56,7 +56,8 @@ app.filter('unsafe', function($sce) {
app.controller('ABBController', function($scope, $http){
$scope.jobs = [];
var min_date = moment().subtract(30,'days').format("YYYY-MM-DD%20HH:mm:ss");
var url = "https://sahkoinsinoorikilta.fi/api/news.php";
// TODO: FIX, we try to get rid of php, not depend on it!
var url = "https://old.sahkoinsinoorikilta.fi/api/news.php";
var params = "?type=11&lang=fi&title_search=ABB&min_date="+min_date
$http.get(url+params).then(function(response){
$scope.jobs = _.filter(response.data, function(job){
+1 -1
View File
@@ -35,6 +35,6 @@ class InfoscreenTestCase(TestCase):
That would mean that something meaningful has been included
in the response.
"""
resp = self.c.get('/infoscreen/items')
resp = self.c.get("/infoscreen/items")
content = resp.json()
self.assertTrue(len(content) > 0)
+23 -22
View File
@@ -27,30 +27,31 @@ from infoscreen.views import createApyItem
from infoscreen.views import get_apy_json
urlpatterns = [
url(r'^$', default),
url(r'^admin$', admin),
url(r'^(?P<idx>\d+)$', index),
url(r'^items$', info_items),
url(r'^rotation/(?P<idx>\d+)$', rotation),
url(r'^rotations$', rotations),
url(r'^instance$', createInstance),
url(r'^instance/(?P<idx>\d+)$', deleteInstance),
url(r'^types$', info_types),
url(r'^delete_item/(?P<type_id>\d+)/(?P<idx>\d+)$', delete_info_item),
url(r'^create_external_image$', createExternalImageInfoItem),
url(r'^create_image$', create_image_item),
url(r'^create_video$', create_video_item),
url(r'^create_abbitem$', createABBItem),
url(r'^create_sossoitem$', createSossoItem),
url(r'^create_lunchitem$', createLunchItem),
url(r'^create_eventitem$', createEventItem),
url(r'^create_apyitem$', createApyItem),
url(r'^create_websiteitem$', createExternalWebsiteItem),
url(r'^create_rotation$', create_rotation),
url(r'^delete_rotation/(?P<id>\d+)$', delete_rotation),
url(r'^apyjson', get_apy_json),
url(r"^$", default),
url(r"^admin$", admin),
url(r"^(?P<idx>\d+)$", index),
url(r"^items$", info_items),
url(r"^rotation/(?P<idx>\d+)$", rotation),
url(r"^rotations$", rotations),
url(r"^instance$", createInstance),
url(r"^instance/(?P<idx>\d+)$", deleteInstance),
url(r"^types$", info_types),
url(r"^delete_item/(?P<type_id>\d+)/(?P<idx>\d+)$", delete_info_item),
url(r"^create_external_image$", createExternalImageInfoItem),
url(r"^create_image$", create_image_item),
url(r"^create_video$", create_video_item),
url(r"^create_abbitem$", createABBItem),
url(r"^create_sossoitem$", createSossoItem),
url(r"^create_lunchitem$", createLunchItem),
url(r"^create_eventitem$", createEventItem),
url(r"^create_apyitem$", createApyItem),
url(r"^create_websiteitem$", createExternalWebsiteItem),
url(r"^create_rotation$", create_rotation),
url(r"^delete_rotation/(?P<id>\d+)$", delete_rotation),
url(r"^apyjson", get_apy_json),
]
if settings.DEBUG:
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
urlpatterns += staticfiles_urlpatterns()
+49 -34
View File
@@ -16,16 +16,27 @@ import threading
import requests
from infoscreen.models import (
Rotation, InfoItem, InfoInstance, ABBInfoItem, ExternalImageInfoItem,
ImageInfoItem, SossoInfoItem, LunchItem, EventInfoItem,
ExternalWebsiteInfoItem, ImageUploadForm, ApyInfoItem, VideoInfoItem)
Rotation,
InfoItem,
InfoInstance,
ABBInfoItem,
ExternalImageInfoItem,
ImageInfoItem,
SossoInfoItem,
LunchItem,
EventInfoItem,
ExternalWebsiteInfoItem,
ImageUploadForm,
ApyInfoItem,
VideoInfoItem,
)
@login_required(login_url='/admin/login')
@permission_required('infoscreen.change_infoinstance', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("infoscreen.change_infoinstance", raise_exception=True)
def admin(request, *args, **kwargs):
"""Render infoscreen admin page."""
return render(request, 'infoscreen:infoscreen_admin.html', {})
return render(request, "infoscreen/infoscreen_admin.html", {})
def create_item_generator(model):
@@ -33,20 +44,23 @@ def create_item_generator(model):
@ensure_csrf_cookie
@require_http_methods(["POST"])
@login_required(login_url='/admin/login')
@permission_required('infoscreen.add_infoinstance', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("infoscreen.add_infoinstance", raise_exception=True)
def create_item(request, *args, **kwargs):
try:
data = json.loads(request.body.decode("utf-8"))
except json.JSONDecodeError:
return HttpResponseBadRequest(
'{"status":"failure","error":"invalid json supplied"}')
'{"status":"failure","error":"invalid json supplied"}'
)
try:
model.create_from_dict(data)
return HttpResponse('{"status":"success"}')
except RuntimeError as e:
return HttpResponseBadRequest(
json.dumps({"status": "failure", "error": str(e)}))
json.dumps({"status": "failure", "error": str(e)})
)
return create_item
@@ -55,8 +69,8 @@ def delete_item_generator(model):
@ensure_csrf_cookie
@require_http_methods(["DELETE"])
@login_required(login_url='/admin/login')
@permission_required('infoscreen.delete_infoinstance', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("infoscreen.delete_infoinstance", raise_exception=True)
def delete_item(request, *args, **kwargs):
idx = kwargs.pop("idx", 0)
try:
@@ -72,13 +86,14 @@ def delete_item_generator(model):
resp = HttpResponse('{"error" : "could not delete item"}')
resp.status_code = 500
return resp
return delete_item
# due to model structure this is little complicated
@ensure_csrf_cookie
@login_required(login_url='/admin/login')
@permission_required('infoscreen.delete_infoinstance', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("infoscreen.delete_infoinstance", raise_exception=True)
@require_http_methods(["DELETE"])
def delete_info_item(request, *args, **kwargs):
"""Delete info item."""
@@ -102,42 +117,44 @@ def delete_info_item(request, *args, **kwargs):
@require_http_methods(["POST"])
@ensure_csrf_cookie
@login_required(login_url='/admin/login')
@permission_required('infoscreen.add_infoinstance', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("infoscreen.add_infoinstance", raise_exception=True)
def create_image_item(request, *args, **kwargs):
"""Create image Infoscreen item."""
form = ImageUploadForm(request.POST, request.FILES)
if not form.is_valid():
return HttpResponseBadRequest('{"status": "failure",'
'"error": "invalid data supplied"}')
return HttpResponseBadRequest(
'{"status": "failure",' '"error": "invalid data supplied"}'
)
img = form.cleaned_data['image']
name = form.cleaned_data['name']
img = form.cleaned_data["image"]
name = form.cleaned_data["name"]
ImageInfoItem.objects.create(img=img, name=name)
return HttpResponse('{"status":"success"}')
@require_http_methods(["POST"])
@ensure_csrf_cookie
@login_required(login_url='/admin/login')
@permission_required('infoscreen.add_infoinstance', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("infoscreen.add_infoinstance", raise_exception=True)
def create_video_item(request, *args, **kwargs):
"""Create video Infoscreen item."""
form = UploadFileForm(request.POST, request.FILES)
if not form.is_valid():
return HttpResponseBadRequest('{"status": "failure",'
'"error": "invalid data supplied"}')
return HttpResponseBadRequest(
'{"status": "failure",' '"error": "invalid data supplied"}'
)
video = form.cleaned_data['video']
name = form.cleaned_data['name']
video = form.cleaned_data["video"]
name = form.cleaned_data["name"]
VideoInfoItem.objects.create(video=video, name=name)
return HttpResponse('{"status": "success"}')
@require_http_methods(["POST"])
@ensure_csrf_cookie
@login_required(login_url='/admin/login')
@permission_required('infoscreen.add_rotation', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("infoscreen.add_rotation", raise_exception=True)
def create_rotation(request, *args, **kwargs):
"""Create rotation."""
try:
@@ -150,16 +167,15 @@ def create_rotation(request, *args, **kwargs):
Rotation.objects.create(name=name)
resp = HttpResponse(status=200)
except DatabaseError:
resp = HttpResponse(
'{"error" : "could not create rotation!"}', status=400)
resp = HttpResponse('{"error" : "could not create rotation!"}', status=400)
return resp
@require_http_methods(["DELETE"])
@ensure_csrf_cookie
@login_required(login_url='/admin/login')
@permission_required('infoscreen.delete_rotation', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("infoscreen.delete_rotation", raise_exception=True)
def delete_rotation(request, *args, **kwargs):
"""Delete rotation."""
id = kwargs.pop("id", 0)
@@ -169,8 +185,7 @@ def delete_rotation(request, *args, **kwargs):
Rotation.objects.filter(id=id).delete()
resp = HttpResponse(status=200)
except DatabaseError:
resp = HttpResponse(
'{"error" : "could not delete rotation!"}', status=400)
resp = HttpResponse('{"error" : "could not delete rotation!"}', status=400)
return resp
+9 -6
View File
@@ -15,7 +15,7 @@ import requests
@require_http_methods(["GET"])
def index(request, idx, *args, **kwargs):
"""Render infoscreen index page."""
return render(request, 'infoscreen_index.html', {'rotation': idx})
return render(request, "infoscreen/infoscreen_index.html", {"rotation": idx})
@require_http_methods(["GET"])
@@ -32,7 +32,8 @@ def default(request, *args, **kwargs):
def get_apy_json(request):
"""Render APY diilikone page."""
return HttpResponse(
requests.get("https://api-diilikone.apy.fi/deals/top-groups").text)
requests.get("https://api-diilikone.apy.fi/deals/top-groups").text
)
@require_http_methods(["GET"])
@@ -61,10 +62,12 @@ def info_types(request, *args, **kwargs):
types = []
classes = InfoItem.get_subclasses()
for c in classes:
types.append({
"name": c.display_name,
"create_template_url": c.get_create_template_url(),
})
types.append(
{
"name": c.display_name,
"create_template_url": c.get_create_template_url(),
}
)
return HttpResponse(json.dumps(types))
+1 -2
View File
@@ -1,10 +1,9 @@
from django.contrib import admin
from modeltranslation.admin import TranslationAdmin
from kaehmy.models import Application, Comment, CustomRole, PresetRole, TelegramChannel
from kaehmy.models import Application, Comment, CustomRole, PresetRole
admin.site.register(Application)
admin.site.register(Comment)
admin.site.register(CustomRole)
admin.site.register(PresetRole, TranslationAdmin)
admin.site.register(TelegramChannel)
+1 -1
View File
@@ -2,4 +2,4 @@ from django.apps import AppConfig
class KaehmyConfig(AppConfig):
name = 'kaehmy'
name = "kaehmy"
+54 -29
View File
@@ -2,16 +2,20 @@ from django import forms
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
from kaehmy.models import PresetRole, CustomRole, Application, Comment, KaehmyBaseRole
from kaehmy.models import PresetRole, CustomRole, Application, Comment, BaseRole
class CheckboxSelectMultiple(forms.widgets.CheckboxSelectMultiple):
option_template_name = 'checkbox_option.html'
option_template_name = "checkbox_option.html"
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
dic = super(CheckboxSelectMultiple, self).create_option(name, value, label, selected, index, subindex, attrs)
description = PresetRole.objects.get(id=value).description
dic['description'] = description
def create_option(
self, name, formIterator, label, selected, index, subindex=None, attrs=None
):
dic = super(CheckboxSelectMultiple, self).create_option(
name, formIterator, label, selected, index, subindex, attrs
)
description = PresetRole.objects.get(id=formIterator.value).description
dic["description"] = description
return dic
def __init__(self, *args, **kwargs):
@@ -25,30 +29,46 @@ class ApplicationForm(forms.ModelForm):
"""Meta for class Application."""
model = Application
fields = ['name', 'email', 'phone_number', 'year',
'preset_roles', 'custom_roles', 'custom_role_name',
'custom_role_is_board', 'text']
fields = [
"name",
"email",
"phone_number",
"year",
"preset_roles",
"custom_roles",
"custom_role_name",
"custom_role_is_board",
"text",
]
def __init__(self, *args, **kwargs):
super(ApplicationForm, self).__init__(*args, **kwargs)
self.fields["email"].label = _('Email (not public)')
self.fields["phone_number"].label = _('Phone number (not public)')
self.fields["email"].label = _("Email (not public)")
self.fields["phone_number"].label = _("Phone number (not public)")
custom_roles_exist = CustomRole.objects.all().exists()
self.fields["custom_roles"].widget = forms.widgets.CheckboxSelectMultiple() if custom_roles_exist else forms.HiddenInput()
self.fields["custom_roles"].widget = (
forms.widgets.CheckboxSelectMultiple()
if custom_roles_exist
else forms.HiddenInput()
)
self.fields["custom_roles"].help_text = ""
self.fields["custom_roles"].label = _('Custom roles')
self.fields["custom_roles"].label = _("Custom roles")
self.fields["custom_roles"].queryset = CustomRole.objects.all()
for cat_id, category in KaehmyBaseRole.CATEGORIES:
key = 'preset_roles_{}'.format(cat_id)
qset = PresetRole.objects.filter(category=cat_id).order_by('category', '-is_board')
for cat_id, category in BaseRole.CATEGORIES:
key = "preset_roles_{}".format(cat_id)
qset = PresetRole.objects.filter(category=cat_id).order_by(
"category", "-is_board"
)
self.fields[key] = forms.ModelMultipleChoiceField(qset)
self.fields[key].widget = CheckboxSelectMultiple(attrs={
'title': _('Preset roles'),
'name': 'preset_roles',
})
self.fields[key].widget = CheckboxSelectMultiple(
attrs={
"title": _("Preset roles"),
"name": "preset_roles",
}
)
self.fields[key].help_text = ""
self.fields[key].queryset = qset
self.fields[key].label = _(category)
@@ -57,33 +77,38 @@ class ApplicationForm(forms.ModelForm):
def clean(self):
cleaned_data = super(ApplicationForm, self).clean()
for key in cleaned_data.keys():
if 'preset_roles_' in key:
cleaned_data['preset_roles'] = cleaned_data['preset_roles'] | cleaned_data[key]
if "preset_roles_" in key:
cleaned_data["preset_roles"] = (
cleaned_data["preset_roles"] | cleaned_data[key]
)
return cleaned_data
def clean_phone_number(self):
"""Clean phone number field."""
number = self.cleaned_data.get('phone_number')
number = self.cleaned_data.get("phone_number")
if number.isdigit():
return number
else:
raise ValidationError(_('Invalid phone number'))
raise ValidationError(_("Invalid phone number"))
def clean_custom_role_name(self):
"""Check that no other custom role with same name exists."""
custom_name = self.cleaned_data.get('custom_role_name')
custom_name = self.cleaned_data.get("custom_role_name")
if not CustomRole.objects.filter(name=custom_name).exists():
return custom_name
else:
raise ValidationError(_('Custom role with the same name already exists.'))
raise ValidationError(_("Custom role with the same name already exists."))
def non_role_fields(self):
return [self.fields[k] for k in self.fields.keys() if k not in ["preset_roles", "custom_roles"]]
return [
self.fields[k]
for k in self.fields.keys()
if k not in ["preset_roles", "custom_roles"]
]
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['name', 'email', 'message', 'parent']
fields = ["name", "email", "message", "parent"]
+147 -41
View File
@@ -12,82 +12,188 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('webapp', '0037_auto_20180125_2131'),
("webapp", "0037_auto_20180125_2131"),
]
operations = [
migrations.CreateModel(
name='CommentParent',
name="CommentParent",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(default='', max_length=255, verbose_name='Name')),
('email', models.EmailField(default='', max_length=254, verbose_name='Email')),
('timestamp', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Timestamp')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"name",
models.CharField(default="", max_length=255, verbose_name="Name"),
),
(
"email",
models.EmailField(default="", max_length=254, verbose_name="Email"),
),
(
"timestamp",
models.DateTimeField(
default=django.utils.timezone.now, verbose_name="Timestamp"
),
),
],
),
migrations.CreateModel(
name='CustomRole',
name="CustomRole",
fields=[
('baserole_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='webapp.BaseRole')),
(
"baserole_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="webapp.BaseRole",
),
),
],
options={
'verbose_name_plural': 'Custom kaehmy roles',
'verbose_name': 'Custom kaehmy role',
"verbose_name_plural": "Custom kaehmy roles",
"verbose_name": "Custom kaehmy role",
},
bases=('webapp.baserole',),
bases=("webapp.baserole",),
),
migrations.CreateModel(
name='PresetRole',
name="PresetRole",
fields=[
('presetrole_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='webapp.PresetRole')),
(
"presetrole_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="webapp.PresetRole",
),
),
],
options={
'verbose_name_plural': 'Preset kaehmy roles',
'verbose_name': 'Preset kaehmy role',
"verbose_name_plural": "Preset kaehmy roles",
"verbose_name": "Preset kaehmy role",
},
bases=('webapp.presetrole',),
bases=("webapp.presetrole",),
),
migrations.CreateModel(
name='TelegramChannel',
name="TelegramChannel",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('channel_id', models.CharField(max_length=255, unique=True)),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255)),
("channel_id", models.CharField(max_length=255, unique=True)),
],
options={
'verbose_name_plural': 'Telegram channels',
'verbose_name': 'Telegram channel',
"verbose_name_plural": "Telegram channels",
"verbose_name": "Telegram channel",
},
),
migrations.CreateModel(
name='Application',
name="Application",
fields=[
('commentparent_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='kaehmy.CommentParent')),
('phone_number', models.CharField(default='', max_length=10, verbose_name='Phone number')),
('year', models.IntegerField(choices=[(1, '1'), (2, '2'), (3, '3'), (4, '4'), (5, 'N')], verbose_name='Year')),
('text', models.TextField(default='', max_length=300, verbose_name='Text')),
('custom_role_name', models.CharField(blank=True, max_length=255, verbose_name='Custom role name')),
('custom_role_is_board', models.BooleanField(verbose_name='Board member')),
('custom_roles', models.ManyToManyField(blank=True, related_name='forms', to='kaehmy.CustomRole')),
('preset_roles', models.ManyToManyField(blank=True, related_name='forms', to='kaehmy.PresetRole')),
(
"commentparent_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="kaehmy.CommentParent",
),
),
(
"phone_number",
models.CharField(
default="", max_length=10, verbose_name="Phone number"
),
),
(
"year",
models.IntegerField(
choices=[(1, "1"), (2, "2"), (3, "3"), (4, "4"), (5, "N")],
verbose_name="Year",
),
),
(
"text",
models.TextField(default="", max_length=300, verbose_name="Text"),
),
(
"custom_role_name",
models.CharField(
blank=True, max_length=255, verbose_name="Custom role name"
),
),
(
"custom_role_is_board",
models.BooleanField(verbose_name="Board member"),
),
(
"custom_roles",
models.ManyToManyField(
blank=True, related_name="forms", to="kaehmy.CustomRole"
),
),
(
"preset_roles",
models.ManyToManyField(
blank=True, related_name="forms", to="kaehmy.PresetRole"
),
),
],
options={
'verbose_name_plural': 'Kaehmylomakkeet',
'verbose_name': 'Kaehmylomake',
"verbose_name_plural": "Kaehmylomakkeet",
"verbose_name": "Kaehmylomake",
},
bases=('kaehmy.commentparent',),
bases=("kaehmy.commentparent",),
),
migrations.CreateModel(
name='Comment',
name="Comment",
fields=[
('commentparent_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='kaehmy.CommentParent')),
('message', models.TextField(verbose_name='Message')),
('parent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='kaehmy.CommentParent')),
(
"commentparent_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="kaehmy.CommentParent",
),
),
("message", models.TextField(verbose_name="Message")),
(
"parent",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="messages",
to="kaehmy.CommentParent",
),
),
],
options={
'verbose_name_plural': 'Kaehmykommentit',
'verbose_name': 'Kaehmykommentti',
"verbose_name_plural": "Kaehmykommentit",
"verbose_name": "Kaehmykommentti",
},
bases=('kaehmy.commentparent',),
bases=("kaehmy.commentparent",),
),
]
+42 -9
View File
@@ -7,26 +7,59 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('webapp', '0047_auto_20180710_2110'),
('kaehmy', '0001_initial'),
("webapp", "0047_auto_20180710_2110"),
("kaehmy", "0001_initial"),
]
operations = [
migrations.CreateModel(
name='KaehmyBaseRole',
name="KaehmyBaseRole",
fields=[
('baserole_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='webapp.BaseRole')),
('category', models.CharField(choices=[('corporate', 'Corporate affairs'), ('freshman', 'Freshmen'), ('international', 'International'), ('external', 'External affairs'), ('media', 'Media'), ('tech', 'Technology'), ('wellbeing', 'Wellbeing'), ('elepaja', 'Elepaja'), ('ceremonies', 'Ceremonies'), ('culture', 'Culture'), ('studies', 'Studies'), ('sosso', 'Sössö magazine'), ('alumni', 'Alumni relations'), ('others', 'Others')], default='others', max_length=255, verbose_name='Category')),
(
"baserole_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="webapp.BaseRole",
),
),
(
"category",
models.CharField(
choices=[
("corporate", "Corporate affairs"),
("freshman", "Freshmen"),
("international", "International"),
("external", "External affairs"),
("media", "Media"),
("tech", "Technology"),
("wellbeing", "Wellbeing"),
("elepaja", "Elepaja"),
("ceremonies", "Ceremonies"),
("culture", "Culture"),
("studies", "Studies"),
("sosso", "Sössö magazine"),
("alumni", "Alumni relations"),
("others", "Others"),
],
default="others",
max_length=255,
verbose_name="Category",
),
),
],
bases=('webapp.baserole',),
bases=("webapp.baserole",),
),
migrations.DeleteModel(
name='Application',
name="Application",
),
migrations.DeleteModel(
name='customrole',
name="customrole",
),
migrations.DeleteModel(
name='presetrole',
name="presetrole",
),
]
+84 -28
View File
@@ -7,57 +7,113 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('kaehmy', '0002_auto_20180902_1929'),
("kaehmy", "0002_auto_20180902_1929"),
]
operations = [
migrations.CreateModel(
name='Application',
name="Application",
fields=[
('commentparent_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='kaehmy.CommentParent')),
('phone_number', models.CharField(default='', max_length=10, verbose_name='Phone number')),
('year', models.IntegerField(choices=[(1, '1'), (2, '2'), (3, '3'), (4, '4'), (5, 'N')], verbose_name='Year')),
('text', models.TextField(default='', max_length=300, verbose_name='Text')),
('custom_role_name', models.CharField(blank=True, max_length=255, verbose_name='Custom role name')),
('custom_role_is_board', models.BooleanField(verbose_name='Board member')),
(
"commentparent_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="kaehmy.CommentParent",
),
),
(
"phone_number",
models.CharField(
default="", max_length=10, verbose_name="Phone number"
),
),
(
"year",
models.IntegerField(
choices=[(1, "1"), (2, "2"), (3, "3"), (4, "4"), (5, "N")],
verbose_name="Year",
),
),
(
"text",
models.TextField(default="", max_length=300, verbose_name="Text"),
),
(
"custom_role_name",
models.CharField(
blank=True, max_length=255, verbose_name="Custom role name"
),
),
(
"custom_role_is_board",
models.BooleanField(verbose_name="Board member"),
),
],
options={
'verbose_name': 'Kaehmylomake',
'verbose_name_plural': 'Kaehmylomakkeet',
"verbose_name": "Kaehmylomake",
"verbose_name_plural": "Kaehmylomakkeet",
},
bases=('kaehmy.commentparent',),
bases=("kaehmy.commentparent",),
),
migrations.CreateModel(
name='CustomRole',
name="CustomRole",
fields=[
('kaehmybaserole_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='kaehmy.KaehmyBaseRole')),
(
"kaehmybaserole_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="kaehmy.KaehmyBaseRole",
),
),
],
options={
'verbose_name': 'Custom kaehmy role',
'verbose_name_plural': 'Custom kaehmy roles',
"verbose_name": "Custom kaehmy role",
"verbose_name_plural": "Custom kaehmy roles",
},
bases=('kaehmy.kaehmybaserole',),
bases=("kaehmy.kaehmybaserole",),
),
migrations.CreateModel(
name='PresetRole',
name="PresetRole",
fields=[
('kaehmybaserole_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='kaehmy.KaehmyBaseRole')),
('description', models.TextField(verbose_name='Description')),
(
"kaehmybaserole_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="kaehmy.KaehmyBaseRole",
),
),
("description", models.TextField(verbose_name="Description")),
],
options={
'verbose_name': 'Preset kaehmy role',
'verbose_name_plural': 'Preset kaehmy roles',
"verbose_name": "Preset kaehmy role",
"verbose_name_plural": "Preset kaehmy roles",
},
bases=('kaehmy.kaehmybaserole',),
bases=("kaehmy.kaehmybaserole",),
),
migrations.AddField(
model_name='application',
name='custom_roles',
field=models.ManyToManyField(blank=True, related_name='forms', to='kaehmy.CustomRole'),
model_name="application",
name="custom_roles",
field=models.ManyToManyField(
blank=True, related_name="forms", to="kaehmy.CustomRole"
),
),
migrations.AddField(
model_name='application',
name='preset_roles',
field=models.ManyToManyField(blank=True, related_name='forms', to='kaehmy.PresetRole'),
model_name="application",
name="preset_roles",
field=models.ManyToManyField(
blank=True, related_name="forms", to="kaehmy.PresetRole"
),
),
]
+23 -4
View File
@@ -6,13 +6,32 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('kaehmy', '0003_auto_20180902_1943'),
("kaehmy", "0003_auto_20180902_1943"),
]
operations = [
migrations.AlterField(
model_name='kaehmybaserole',
name='category',
field=models.CharField(choices=[('corporate', 'Corporate affairs'), ('freshman', 'Freshmen'), ('international', 'International'), ('external', 'External affairs'), ('media', 'Media'), ('tech', 'Technology'), ('wellbeing', 'Wellbeing'), ('elepaja', 'Elepaja'), ('ceremonies', 'Ceremonies'), ('studies', 'Studies'), ('sosso', 'Sössö magazine'), ('alumni', 'Alumni relations'), ('others', 'Others')], default='others', max_length=255, verbose_name='Category'),
model_name="kaehmybaserole",
name="category",
field=models.CharField(
choices=[
("corporate", "Corporate affairs"),
("freshman", "Freshmen"),
("international", "International"),
("external", "External affairs"),
("media", "Media"),
("tech", "Technology"),
("wellbeing", "Wellbeing"),
("elepaja", "Elepaja"),
("ceremonies", "Ceremonies"),
("studies", "Studies"),
("sosso", "Sössö magazine"),
("alumni", "Alumni relations"),
("others", "Others"),
],
default="others",
max_length=255,
verbose_name="Category",
),
),
]
+4 -4
View File
@@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('kaehmy', '0004_auto_20181018_2121'),
("kaehmy", "0004_auto_20181018_2121"),
]
operations = [
migrations.AlterField(
model_name='application',
name='custom_role_is_board',
field=models.BooleanField(blank=True, verbose_name='Board member'),
model_name="application",
name="custom_role_is_board",
field=models.BooleanField(blank=True, verbose_name="Board member"),
),
]
@@ -0,0 +1,16 @@
# Generated by Django 2.2.26 on 2022-01-12 20:38
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("kaehmy", "0005_auto_20190312_1458"),
]
operations = [
migrations.DeleteModel(
name="TelegramChannel",
),
]
@@ -0,0 +1,18 @@
# Generated by Django 3.2.14 on 2022-08-01 19:13
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("kaehmy", "0006_delete_telegramchannel"),
]
operations = [
migrations.AlterField(
model_name="commentparent",
name="id",
field=models.AutoField(primary_key=True, serialize=False),
),
]
+44
View File
@@ -0,0 +1,44 @@
# Generated by Django 3.2.14 on 2022-08-03 20:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("kaehmy", "0007_alter_commentparent_id"),
]
operations = [
migrations.CreateModel(
name="BaseRole",
fields=[
("id", models.AutoField(primary_key=True, serialize=False)),
("name", models.CharField(max_length=255, verbose_name="Name")),
("is_board", models.BooleanField(verbose_name="Board member")),
(
"category",
models.CharField(
choices=[
("corporate", "Corporate affairs"),
("freshman", "Freshmen"),
("international", "International"),
("external", "External affairs"),
("media", "Media"),
("tech", "Technology"),
("wellbeing", "Wellbeing"),
("elepaja", "Elepaja"),
("ceremonies", "Ceremonies"),
("studies", "Studies"),
("sosso", "Sössö magazine"),
("alumni", "Alumni relations"),
("others", "Others"),
],
default="others",
max_length=255,
verbose_name="Category",
),
),
],
),
]
@@ -0,0 +1,29 @@
# Generated by Django 2.2.28 on 2022-07-26 17:15
from unicodedata import category
from django.db import migrations
def copyBaseRolesToNewTable(apps, schema_editor):
Old = apps.get_model("kaehmy", "KaehmyBaseRole")
New = apps.get_model("kaehmy", "BaseRole")
for bases in Old.objects.all():
New.objects.create(
id=bases.id,
name=bases.name,
is_board=bases.is_board,
category=bases.category,
)
class Migration(migrations.Migration):
dependencies = [
("kaehmy", "0008_baserole"),
]
operations = [
migrations.RunPython(
copyBaseRolesToNewTable, reverse_code=migrations.RunPython.noop
),
]
@@ -0,0 +1,51 @@
# Generated by Django 2.2.28 on 2022-07-26 17:33
from django.db import migrations, models
import django.db.models.deletion
from sikweb.custom_operations import AlterModelBases
class Migration(migrations.Migration):
dependencies = [
("kaehmy", "0009_auto_20220726_2015"),
]
operations = [
AlterModelBases("customrole", (models.Model,)),
AlterModelBases("presetrole", (models.Model,)),
migrations.AlterField(
model_name="customrole",
name="kaehmybaserole_ptr",
field=models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="BaseRole",
),
),
migrations.AlterField(
model_name="presetrole",
name="kaehmybaserole_ptr",
field=models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="BaseRole",
),
),
migrations.RenameField(
model_name="customrole",
old_name="kaehmybaserole_ptr",
new_name="baserole_ptr",
),
migrations.RenameField(
model_name="presetrole",
old_name="kaehmybaserole_ptr",
new_name="baserole_ptr",
),
]
@@ -0,0 +1,16 @@
# Generated by Django 3.2.14 on 2022-08-03 20:19
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("kaehmy", "0010_auto_20220726_2033"),
]
operations = [
migrations.DeleteModel(
name="KaehmyBaseRole",
),
]
+81 -77
View File
@@ -1,64 +1,71 @@
from django.db import models
from django.utils import timezone
from datetime import timedelta
from webapp.utils import month_from_now
from django.utils.translation import ugettext_lazy as _
from auditlog.registry import auditlog
from phonenumber_field.modelfields import PhoneNumberField
import logging
import webapp.models
VERBOSE_NAME = _('Kaehmy')
VERBOSE_NAME = _("Kaehmy")
class KaehmyBaseRole(webapp.models.BaseRole):
class BaseRole(models.Model):
"""Base model for occupations/roles."""
id = models.AutoField(primary_key=True)
name = models.CharField(_("Name"), max_length=255)
is_board = models.BooleanField(_("Board member"))
CATEGORIES = (
('corporate', _('Corporate affairs')),
('freshman', _('Freshmen')),
('international', _('International')),
('external', _('External affairs')),
('media', _('Media')),
('tech', _('Technology')),
('wellbeing', _('Wellbeing')),
('elepaja', _('Elepaja')),
('ceremonies', _('Ceremonies')),
('studies', _('Studies')),
('sosso', _('Sössö magazine')),
('alumni', _('Alumni relations')),
('others', _('Others')),
("board", _("Board")),
("corporate", _("Corporate affairs")),
("freshman", _("Freshmen")),
("international", _("International")),
("external", _("External affairs")),
("media", _("Media")),
("tech", _("Technology")),
("wellbeing", _("Wellbeing")),
("sikpaja", _("Sik-paja")),
("ceremonies", _("Ceremonies")),
("studies", _("Studies")),
("sosso", _("Sössö magazine")),
("pota", _("PoTa")),
("alumni", _("Alumni relations")),
("n", _("N")),
("others", _("Others")),
)
category = models.CharField(_('Category'), choices=CATEGORIES, default='others', max_length=255)
category = models.CharField(
_("Category"), choices=CATEGORIES, default="others", max_length=255
)
def __str__(self):
n = self.name.capitalize()
return "{} ({})".format(n, _("board member")) if self.is_board else n
class PresetRole(KaehmyBaseRole):
class PresetRole(BaseRole):
"""Model for kaehmy role."""
description = models.TextField(_('Description'))
description = models.TextField(_("Description"))
class Meta:
verbose_name = _('Preset kaehmy role')
verbose_name_plural = _('Preset kaehmy roles')
verbose_name = _("Preset kaehmy role")
verbose_name_plural = _("Preset kaehmy roles")
class CustomRole(KaehmyBaseRole):
class CustomRole(BaseRole):
"""Model representing a user-specified custom occupation."""
class Meta:
verbose_name = _('Custom kaehmy role')
verbose_name_plural = _('Custom kaehmy roles')
verbose_name = _("Custom kaehmy role")
verbose_name_plural = _("Custom kaehmy roles")
class CommentParent(models.Model):
name = models.CharField(_('Name'), max_length=255, default='')
email = models.EmailField(_('Email'), default='')
timestamp = models.DateTimeField(_('Timestamp'), default=timezone.now)
id = models.AutoField(primary_key=True)
name = models.CharField(_("Name"), max_length=255, default="")
email = models.EmailField(_("Email"), default="")
timestamp = models.DateTimeField(_("Timestamp"), default=timezone.now)
def __str__(self):
return 'Message parent #{}'.format(self.id)
return "Message parent #{}".format(self.id)
class Comment(CommentParent):
@@ -69,11 +76,13 @@ class Comment(CommentParent):
"""
class Meta:
verbose_name = _('Kaehmykommentti')
verbose_name_plural = _('Kaehmykommentit')
verbose_name = _("Kaehmykommentti")
verbose_name_plural = _("Kaehmykommentit")
message = models.TextField(_('Message'))
parent = models.ForeignKey('CommentParent', related_name='messages', on_delete=models.CASCADE)
message = models.TextField(_("Message"))
parent = models.ForeignKey(
"CommentParent", related_name="messages", on_delete=models.CASCADE
)
class Application(CommentParent):
@@ -82,34 +91,36 @@ class Application(CommentParent):
Allows user to choose from existing roles or to create custom ones.
"""
YEAR_CHOICES = (
(1, '1'),
(2, '2'),
(3, '3'),
(4, '4'),
(5, 'N'),
(1, "1"),
(2, "2"),
(3, "3"),
(4, "4"),
(5, "N"),
)
class Meta:
verbose_name = _('Kaehmylomake')
verbose_name_plural = _('Kaehmylomakkeet')
verbose_name = _("Kaehmylomake")
verbose_name_plural = _("Kaehmylomakkeet")
phone_number = models.CharField(
_('Phone number'), max_length=10, default="")
year = models.IntegerField(_('Year'), choices=YEAR_CHOICES)
text = models.TextField(_('Text'), default="", max_length=300)
phone_number = models.CharField(_("Phone number"), max_length=10, default="")
year = models.IntegerField(_("Year"), choices=YEAR_CHOICES)
text = models.TextField(_("Text"), default="", max_length=300)
custom_role_name = models.CharField(
_('Custom role name'), max_length=255, blank=True)
custom_role_is_board = models.BooleanField(
_('Board member'), blank=True)
_("Custom role name"), max_length=255, blank=True
)
custom_role_is_board = models.BooleanField(_("Board member"), blank=True)
custom_roles = models.ManyToManyField(
'kaehmy.CustomRole', related_name='forms', blank=True)
"kaehmy.CustomRole", related_name="forms", blank=True
)
preset_roles = models.ManyToManyField(
'kaehmy.PresetRole', related_name='forms', blank=True)
"kaehmy.PresetRole", related_name="forms", blank=True
)
def __str__(self):
"""Return model info."""
return _('Kaehmy application: {}').format(self.name)
return _("Kaehmy application: {}").format(self.name)
def comment_count(self):
"""Count comments for kaehmy."""
@@ -131,34 +142,27 @@ class Application(CommentParent):
presets = [r.name.capitalize() for r in self.preset_roles.filter(is_board=True)]
customs = [r.name.capitalize() for r in self.custom_roles.filter(is_board=True)]
combined = presets + customs
return _('Board: {}').format(', '.join(combined)) if len(combined) > 0 else ''
return _("Board: {}").format(", ".join(combined)) if len(combined) > 0 else ""
def official_roles(self):
presets = [r.name.capitalize() for r in self.preset_roles.filter(is_board=False)]
customs = [r.name.capitalize() for r in self.custom_roles.filter(is_board=False)]
presets = [
r.name.capitalize() for r in self.preset_roles.filter(is_board=False)
]
customs = [
r.name.capitalize() for r in self.custom_roles.filter(is_board=False)
]
combined = presets + customs
return _('Official: {}').format(', '.join(combined)) if len(combined) > 0 else ''
return (
_("Official: {}").format(", ".join(combined)) if len(combined) > 0 else ""
)
def all_roles(self):
presets = [r.name.capitalize() for r in self.preset_roles.all()]
customs = [r.name.capitalize() for r in self.custom_roles.all()]
combined = presets + customs
return ', '.join(combined) if len(combined) > 0 else ''
return ", ".join(combined) if len(combined) > 0 else ""
def has_any_board_role(self):
return self.preset_roles.filter(is_board=True).exists() or self.custom_roles.filter(is_board=True)
# Telegram channel entry for Kaehmys
class TelegramChannel(models.Model):
"""Model containing the channel id of a Telegram chat"""
class Meta:
verbose_name = _('Telegram channel')
verbose_name_plural = _('Telegram channels')
name = models.CharField(max_length=255)
channel_id = models.CharField(max_length=255, unique=True)
def __str__(self):
return 'Telegram channel: "{}"'.format(self.name)
return self.preset_roles.filter(
is_board=True
).exists() or self.custom_roles.filter(is_board=True)
+4 -6
View File
@@ -5,12 +5,6 @@
margin-right: auto;
}
body {
max-width: 1000px;
margin-left: auto !important;
margin-right: auto !important;
}
div.tooltip-inner {
max-width: 25rem;
}
@@ -28,6 +22,10 @@ div.tooltip-inner {
.kaehmy-content {
padding-left: 0.5rem;
padding-right: 0.5rem;
max-width: 1000px;
width: 100%;
margin-left: auto;
margin-right: auto;
}
p {
+2 -5
View File
@@ -3,13 +3,9 @@
}
footer {
/* position: absolute; */
bottom: 0;
width: 100%;
height: 60px; /* Set the fixed height of the footer here */
/* line-height: 60px; /* Vertically center the text there */
margin-top: 2rem;
margin-bottom: 1rem;
margin: 1rem;
}
footer .container .col .nav .nav-item {
@@ -26,6 +22,7 @@ footer .container .col .nav .nav-item {
.lang-select {
width: 10rem;
margin-bottom: 1rem;
display: inline-block;
}
+18 -27
View File
@@ -1,37 +1,28 @@
.header-content {
.kaehmy-header {
background-color: #0c2938;
}
.header-content .logo {
.kaehmy-header-content {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
}
.header-content .logo img {
display: block;
height: auto;
margin: auto;
}
.kaehmy-banner {
max-width: 1000px;
width: 100%;
margin-left: auto;
margin-right: auto;
}
@media screen and (min-width: 1000px) {
.kaehmy_header-content {
position: absolute;
left: 0;
top: 0;
background-color: #052f5f;
width: 100%;
}
.kaehmy_header {
margin-bottom: 331px;
}
}
.kaehmy-banner-image {
width: 100%;
max-height: 10rem;
max-width: 100%;
}
.heading {
display: flex;
place-content: center;
flex-direction: column;
text-align: center;
margin: 1rem;
}
+7 -3
View File
@@ -1,11 +1,15 @@
.kaehmy_navigation {
margin-bottom: 10px;
}
.navbar-border {
border-bottom: 2px solid #282b3b;
}
.navbar-light .navbar-nav .nav-link {
color: black;
}
.navbar {
max-width: 1000px;
width: 100%;
margin-left: auto;
margin-right: auto;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

After

Width:  |  Height:  |  Size: 88 KiB

+8 -2
View File
@@ -8,6 +8,12 @@ from kaehmy.models import Application
class ExportTable(tables.Table):
class Meta:
model = Application
exclude = ['text', 'messageparent_ptr', 'custom_role_name', 'custom_role_is_board', 'timestamp']
exclude = [
"text",
"messageparent_ptr",
"custom_role_name",
"custom_role_is_board",
"timestamp",
]
all_roles = tables.Column(verbose_name=_('Roles'), orderable=False)
all_roles = tables.Column(verbose_name=_("Roles"), orderable=False)
+2 -3
View File
@@ -1,5 +1,4 @@
{% if wrap_label %}
<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>{% endif %}
<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>
{% include "django/forms/widgets/input.html" %}
{% if wrap_label %} {{ widget.label }}</label>{% endif %}
{{ widget.label }}</label>
<span class="fa fa-info-circle" data-toggle="tooltip" data-placement="right" title="{{ widget.description }}"></span>
-24
View File
@@ -1,24 +0,0 @@
{% load i18n %}
{% load static %}
{% load staticfiles %}
<footer style="text-align: center">
<div>
<form class="lang-form form" action="{% url 'set_language' %}" method="post">{% csrf_token %}
<span>
<input name="next" type="hidden" value="{{ redirect_to }}" />
<select onchange="this.form.submit()" class="lang-select form-control" name="language">
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% get_language_info_list for LANGUAGES as languages %}
{% for language in languages %}
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
{{ language.name_local }} ({{ language.code }})
</option>
{% endfor %}
</select>
</span>
</form>
<span>{% trans "Copyright Aalto-yliopiston Sähköinsinöörikilta ry" %} {% now 'Y' %}</span>
</div>
</footer>
-7
View File
@@ -1,7 +0,0 @@
{% load i18n %}
<div class="kaehmy_header-content">
<div class="kaehmy-banner logo">
<a href="/kaehmy"><img class="kaehmy-banner-image" src="/static/img/kaehmy_banner.png" alt="Aalto-yliopiston Sähköinsinöörikilta ry"></a>
</div>
</div>
-38
View File
@@ -1,38 +0,0 @@
'''
A telegram bot api for whatever purposes.
TODO: kaehmy app is definitely not correct place for this
'''
import logging
import requests
from django.conf import settings
from kaehmy.models import TelegramChannel
class TelegramBot:
'''
A telegram bot api for whatever purposes
Currently only able to broadcast stuff to all registered
channels using broadcast method.
'''
def __init__(self, api_token=None):
self.api_token = api_token or settings.TELEGRAM_BOT_TOKEN
self.send_message_url = "https://api.telegram.org/bot{}/sendMessage".format(self.api_token)
def broadcast(self, message):
channels_ids = TelegramChannel.objects.values_list("channel_id", flat=True)
for id_ in channels_ids:
self.send_message(id_, message)
def send_message(self, channel_id, message):
'''
Send message to a chat with given channel_id
'''
data = {
'chat_id': channel_id,
'text': message,
'parse_mode': 'Markdown'
}
resp = requests.post(self.send_message_url, json=data)
logging.debug(resp.content)
+2 -2
View File
@@ -6,13 +6,13 @@ from kaehmy.models import PresetRole, CustomRole
@register(PresetRole)
class PresetRoleTranslationOptions(TranslationOptions):
""" Class for PresetRole translation options"""
"""Class for PresetRole translation options"""
fields = ()
@register(CustomRole)
class CustomRoleTranslationOptions(TranslationOptions):
""" Class for CustomROle translation options"""
"""Class for CustomROle translation options"""
fields = ()
+7 -6
View File
@@ -13,14 +13,15 @@ from kaehmy.views import export_view
urlpatterns = [
# kaehmy
url(r'^new', view),
url(r'^submit', submit),
url(r'^add_comment', comment),
url(r'^statistics', statistics_view),
url(r'^export', export_view),
url(r'^$', list_view),
url(r"^new", view),
url(r"^submit", submit),
url(r"^add_comment", comment),
url(r"^statistics", statistics_view),
url(r"^export", export_view),
url(r"^$", list_view),
]
if settings.DEBUG:
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
urlpatterns += staticfiles_urlpatterns()
+74 -78
View File
@@ -1,22 +1,20 @@
from django.db.models import Count
from django.shortcuts import render, redirect
from django.contrib.auth import login, logout, authenticate
from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import ensure_csrf_cookie
from django.http import HttpResponse, HttpResponseRedirect
from django.contrib.auth.decorators import permission_required, login_required
from django.conf import settings
from django.core.mail import send_mail
from django.http import HttpResponseRedirect
from django.contrib.auth.decorators import login_required
from django.template.loader import render_to_string
import logging
import requests
from dealer.git import git
from sikweb.settings import URL
from members.views.utils import *
from kaehmy.models import Application, CustomRole, PresetRole, TelegramChannel
from kaehmy.models import Application, CustomRole, PresetRole
from kaehmy.forms import ApplicationForm, CommentForm
from kaehmy.tables import ExportTable
from webapp.utils import send_email
from webapp.models import processHooks
@ensure_csrf_cookie
@@ -24,28 +22,38 @@ from webapp.utils import send_email
def list_view(request, *args, **kwargs):
"""Kaehmy application list"""
role_filter = request.GET.get('role', None)
if role_filter is not None and str(role_filter) != '-1':
applications = Application.objects.filter(custom_roles__id=role_filter) | Application.objects.filter(preset_roles__id=role_filter)
role_filter = request.GET.get("role", None)
if role_filter is not None and str(role_filter) != "-1":
applications = Application.objects.filter(
custom_roles__id=role_filter
) | Application.objects.filter(preset_roles__id=role_filter)
else:
applications = Application.objects.all()
applications = applications.order_by('-timestamp')
filter_options_preset = PresetRole.objects.annotate(form_count=Count('forms')).filter(form_count__gt=0)
filter_options_preset_list = [(r.id, r.name, r.form_count) for r in filter_options_preset]
filter_options_custom = CustomRole.objects.annotate(form_count=Count('forms')).filter(form_count__gt=0)
filter_options_custom_list = [(r.id, r.name, r.form_count) for r in filter_options_custom]
applications = applications.order_by("-timestamp")
filter_options_preset = PresetRole.objects.annotate(
form_count=Count("forms")
).filter(form_count__gt=0)
filter_options_preset_list = [
(r.id, r.name, r.form_count) for r in filter_options_preset
]
filter_options_custom = CustomRole.objects.annotate(
form_count=Count("forms")
).filter(form_count__gt=0)
filter_options_custom_list = [
(r.id, r.name, r.form_count) for r in filter_options_custom
]
filter_options = filter_options_preset_list + filter_options_custom_list
filter_options.sort(key=lambda f: f[1])
context = {
'applications': applications,
'application_count': len(applications),
'filter_options': filter_options
"applications": applications,
"application_count": len(applications),
"filter_options": filter_options,
}
return render(request, 'kaehmy:list.html', context)
return render(request, "kaehmy/list.html", context)
@ensure_csrf_cookie
@@ -56,23 +64,22 @@ def comment(request, *args, **kwargs):
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save()
email = comment.parent.email
name = comment.name
url = f"https://{URL}/kaehmy"
subject = 'Kaehmyysi tai kommenttiisi on vastattu!'
body = ('{} on vastannut kaehmyhakemukseesi tai kommenttiisi kaehmypalvelussa.\r\n\r\n'
'Käy lukemassa viesti osoitteessa http://sika.sahkoinsinoorikilta.fi/kaehmy').format(name.capitalize())
to_email = comment.parent.email
subject = "Kaehmyysi tai kommenttiisi on vastattu!"
message = render_to_string(
"kaehmy/email_comment.html", {"name": name, "url": url}
)
send_email(email, subject, body)
logging.debug(
'Sent kaehmy comment email to recipient <{}>'.format(email))
send_email(to=to_email, subject=subject, body=message, html=True)
logging.debug(f"Sent kaehmy comment email to recipient <{to_email}>")
return redirect('/kaehmy')
return redirect("/kaehmy")
else:
context = {
'error': form.errors
}
return render(request, 'kaehmy:error.html', context)
context = {"error": form.errors}
return render(request, "kaehmy/error.html", context)
@require_http_methods(["GET"])
@@ -87,24 +94,24 @@ def statistics_view(request, *args, **kwargs):
for preset in preset_roles:
people = [form.name for form in preset.forms.all()]
role_list.append((preset.name, len(people), ', '.join(people)))
role_list.append((preset.name, len(people), ", ".join(people)))
for custom in custom_roles:
people = [form.name for form in custom.forms.all()]
role_list.append((custom.name, len(people), ', '.join(people)))
role_list.append((custom.name, len(people), ", ".join(people)))
context = {
'applications': applications,
'application_count': len(applications),
'role_list': role_list
"applications": applications,
"application_count": len(applications),
"role_list": role_list,
}
return render(request, 'kaehmy:statistics.html', context)
return render(request, "kaehmy/statistics.html", context)
@require_http_methods(["GET"])
def view(request, *args, **kwargs):
"""Render Kaehmy form page."""
form = ApplicationForm()
return render(request, 'kaehmy:kaehmy.html', {'form': form})
return render(request, "kaehmy/kaehmy.html", {"form": form})
@ensure_csrf_cookie
@@ -114,56 +121,45 @@ def submit(request, *args, **kwargs):
form = ApplicationForm(request.POST)
if form.is_valid():
application = form.save()
custom_name = form.cleaned_data.get('custom_role_name')
custom_is_board = form.cleaned_data.get('custom_role_is_board')
custom_name = form.cleaned_data.get("custom_role_name")
custom_is_board = form.cleaned_data.get("custom_role_is_board")
if len(custom_name) > 0:
custom_role = CustomRole(
name=custom_name, is_board=custom_is_board)
custom_role = CustomRole(name=custom_name, is_board=custom_is_board)
custom_role.save()
application.custom_roles.add(custom_role)
url = 'https://sika.sahkoinsinoorikilta.fi/kaehmy'
url = f"https://{URL}/kaehmy"
name = form.cleaned_data.get("name", "Anonymous")
email = form.cleaned_data.get('email', '')
name = form.cleaned_data.get('name', 'Anonymous')
subject = 'Arwokas kirjattu kirje mahdolliselle tulewalle kiltahenkilölle'
body = ('Moikka {}!\r\n\r\nHienoa, että kilta kiinnostaa! Kaehmysi on vastaanotettu.\r\n'
'Mahdollisista kommenteista tulee ilmoitus sähköpostitse.\r\n\r\n'
'Käy katsomassa kaehmytilanne osoitteessa {}').format(name, url)
to_email = form.cleaned_data.get("email", "")
subject = "Arwokas kirjattu kirje mahdolliselle tulewalle kiltahenkilölle"
message = render_to_string(
"kaehmy/email_kaehmy.html", {"name": name, "url": url}
)
send_email(email, subject, body)
logging.debug('Sent kaehmy email to recipient <{}>'.format(email))
CHAT_IDS = [channel.channel_id for channel in TelegramChannel.objects.all()]
for chat_id in CHAT_IDS:
tg_string = 'https://api.telegram.org/bot{}/sendMessage?chat_id={}&text={}'.format(
settings.TELEGRAM_BOT_TOKEN,
chat_id,
'Uusi New kaehmy! {} -> {}'.format(name, url)
)
response = requests.get(tg_string).json()
logging.debug('Telegram API response:\n{}'.format(response))
logging.debug('Sent kaehmy announcement to {} channels.'.format(len(CHAT_IDS)))
send_email(to=to_email, subject=subject, body=message, html=True)
logging.debug(f"Sent kaehmy email to recipient <{to_email}>")
processHooks(message=f"Uusi New kaehmy! {name} -> {url}", eventType="kaehmy")
else:
context = {
'error': form.errors
}
return render(request, 'kaehmy:error.html', context)
return HttpResponseRedirect('/kaehmy')
context = {"error": form.errors}
return render(request, "kaehmy/error.html", context)
return HttpResponseRedirect("/kaehmy")
@require_http_methods(['GET'])
@login_required(login_url='/admin/login')
@require_http_methods(["GET"])
@login_required(login_url="/admin/login")
def export_view(request, *args, **kwargs):
def make_table(queryset):
table = ExportTable(queryset,
request=request,
exclude=['id'],
attrs={'class': 'table table-bordered table-hover'})
table = ExportTable(
queryset,
request=request,
exclude=["id"],
attrs={"class": "table table-bordered table-hover"},
)
table.paginate(page=request.GET.get('page', 1), per_page=9999)
table.paginate(page=request.GET.get("page", 1), per_page=9999)
table_html = convert_table_to_html(table, request)
return table_html
@@ -172,7 +168,7 @@ def export_view(request, *args, **kwargs):
board = filter(lambda q: q.has_any_board_role(), kaehmys)
context = {
'non_board_table': make_table(non_board),
'board_table': make_table(board),
"non_board_table": make_table(non_board),
"board_table": make_table(board),
}
return render(request, 'kaehmy:export.html', context)
return render(request, "kaehmy/export.html", context)
-75
View File
@@ -1,75 +0,0 @@
# Linux/Mac installation instructions
## Install dependencies
### Dependency list
* Python >3.5
* PostgreSQL >9.5
* pip3
* virtualenv
* npm
Install with apt:
```bash
sudo apt install python3
sudo apt install python3-pip
sudo apt install postgresql
sudo pip3 install virtualenv
sudo apt install npm
```
More info about PostgreSQL at:
[https://www.postgresql.org/](https://www.postgresql.org)
These packages might be needed on certain platforms:
* python3-dev
* libffi-dev
* python3-cffi
* libssl-dev
## Create a virtual environment for python
Create a virtualenv in the parent directory.
```bash
virtualenv -p python3 ../virtualenv.sikweb
```
## Activate virtualenv
Assuming we are at the root of this repository and virtualenv is one level above.
```bash
. ../virtualenv.sikweb/bin/activate
```
## Run install wizard
Run the install wizard with
```bash
bash setup.sh
```
and follow the instructions.
## Done
## In case of error on macOS Mojave 10.14
If you get an error saying
```bash
The headers or library files could not be found for zlib,
a required dependency when compiling Pillow from source.
```
run
```bash
xcode-select --install
sudo installer -pkg /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg -target /
```
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -8,4 +8,4 @@ admin.site.register(Member)
admin.site.register(Request)
admin.site.register(Payment)
admin.site.site_header = 'SIK Admin'
admin.site.site_header = "SIK Admin"
+1 -1
View File
@@ -6,4 +6,4 @@ from django.apps import AppConfig
class MembersConfig(AppConfig):
"""Class for Members app configurations."""
name = 'members'
name = "members"
+25 -22
View File
@@ -23,7 +23,7 @@ class MemberForm(forms.ModelForm):
"""Meta for Member model form."""
model = Member
fields = ['first_name', 'last_name', 'email', 'POR', 'AYY', 'jas']
fields = ["first_name", "last_name", "email", "POR", "AYY", "jas"]
class ImportResult:
def __init__(self, members, payments):
@@ -32,22 +32,27 @@ class MemberForm(forms.ModelForm):
def _clean_boolean_field(self, key):
value = self.data.get(key, None)
if value in ['1', '0']:
if value in ["1", "0"]:
return bool(int(value))
else:
return value == 'on'
return value == "on"
def clean_jas(self):
return self._clean_boolean_field('jas')
return self._clean_boolean_field("jas")
def clean_AYY(self):
return self._clean_boolean_field('AYY')
return self._clean_boolean_field("AYY")
@staticmethod
def csv_to_models(data, payment_source='AYY', delimiter=','):
clean_data = data.strip().split('\n')
clean_data = [row.rstrip(',').rstrip('\r').strip() for row in clean_data]
csv_reader = csv.DictReader(clean_data, fieldnames=MemberForm.Meta.fields, delimiter=delimiter, quoting=csv.QUOTE_NONE)
def csv_to_models(data, payment_source="AYY", delimiter=","):
clean_data = data.strip().split("\n")
clean_data = [row.rstrip(",").rstrip("\r").strip() for row in clean_data]
csv_reader = csv.DictReader(
clean_data,
fieldnames=MemberForm.Meta.fields,
delimiter=delimiter,
quoting=csv.QUOTE_NONE,
)
members = []
payments = []
@@ -57,10 +62,10 @@ class MemberForm(forms.ModelForm):
line[key] = value.strip()
except AttributeError as ex:
logging.error('Invalid line in CSV: "{}"'.format(line))
logging.error('Delimiter: {}'.format(delimiter))
logging.error("Delimiter: {}".format(delimiter))
raise
email = line['email']
email = line["email"]
member_exists = False
if Member.objects.filter(email=email).exists():
member_exists = True
@@ -76,9 +81,9 @@ class MemberForm(forms.ModelForm):
else:
member = Member.objects.get(email=email)
payment_data = {
'source': payment_source,
'member': member.id,
'date': timezone.now(),
"source": payment_source,
"member": member.id,
"date": timezone.now(),
}
form = PaymentForm(payment_data)
if not form.is_valid():
@@ -95,17 +100,15 @@ class PaymentForm(forms.ModelForm):
member = forms.ModelChoiceField(
queryset=Member.objects.all(),
widget=autocomplete.ModelSelect2(url='member-autocomplete')
widget=autocomplete.ModelSelect2(url="member-autocomplete"),
)
class Meta:
"""Meta for Payment model form."""
model = Payment
fields = ['date', 'source', 'member']
labels = {
'member': _('Member')
}
fields = ["date", "source", "member"]
labels = {"member": _("Member")}
class ApplicationForm(forms.ModelForm):
@@ -115,13 +118,13 @@ class ApplicationForm(forms.ModelForm):
"""Meta for application model form."""
model = Request
fields = ['first_name', 'last_name', 'email', 'AYY', 'jas', 'POR']
fields = ["first_name", "last_name", "email", "AYY", "jas", "POR"]
def __init__(self, *args, **kwargs):
super(ApplicationForm, self).__init__(*args, **kwargs)
self.fields['AYY'].label = _("I'm a member of AYY")
self.fields['jas'].label = _("I want to receive a weekly newsletter")
self.fields["AYY"].label = _("I'm a member of AYY")
self.fields["jas"].label = _("I want to receive a weekly newsletter")
class UploadFileForm(forms.Form):
@@ -1,30 +0,0 @@
from django.core.management.base import BaseCommand, CommandError
from django.contrib.auth.models import User, Permission
from rest_framework.authtoken.models import Token
class Command(BaseCommand):
user_name = "sahkopiikki"
password = User.objects.make_random_password()
def handle(self, *args, **options):
if User.objects.filter(username=self.user_name).exists():
self.stdout.write("Sahkopiikki user already exists. Skipping.")
user = User.objects.get(username=self.user_name)
token = Token.objects.get(user=user)
self.stdout.write("Token: {}".format(token))
return
u = User(username=self.user_name)
u.set_password(self.password)
u.save()
permission = Permission.objects.get(codename='check_by_email')
u.user_permissions.add(permission)
token = Token.objects.create(user=u)
self.stdout.write("Created sahkopiikki user '{}' with password '{}' and token '{}'.".format(
self.user_name, self.password, token
))
+38 -15
View File
@@ -12,29 +12,52 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
]
dependencies = []
operations = [
migrations.CreateModel(
name='Member',
name="Member",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_name', models.CharField(max_length=127)),
('last_name', models.CharField(max_length=127)),
('email', models.EmailField(max_length=254)),
('POR', models.CharField(max_length=255)),
('AYY', models.BooleanField(default=False)),
('jas', models.BooleanField(default=False)),
('created', models.DateTimeField(default=django.utils.timezone.now)),
('paid', models.DateTimeField(default=datetime.datetime(1970, 1, 1, 0, 0))),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("first_name", models.CharField(max_length=127)),
("last_name", models.CharField(max_length=127)),
("email", models.EmailField(max_length=254)),
("POR", models.CharField(max_length=255)),
("AYY", models.BooleanField(default=False)),
("jas", models.BooleanField(default=False)),
("created", models.DateTimeField(default=django.utils.timezone.now)),
(
"paid",
models.DateTimeField(default=datetime.datetime(1970, 1, 1, 0, 0)),
),
],
),
migrations.CreateModel(
name='MemberRequest',
name="MemberRequest",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('member', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='members.Member')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"member",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="members.Member"
),
),
],
),
]
@@ -9,13 +9,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0001_initial'),
("members", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name='member',
name='paid',
model_name="member",
name="paid",
field=models.DateTimeField(default=datetime.datetime(1970, 1, 1, 2, 0)),
),
]

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