316 Commits

Author SHA1 Message Date
Simeon Pursiainen 8164094b44 Edit email.html 2025-01-23 19:13:16 +00:00
Simeon Pursiainen fd12f35e2e Update .gitlab-ci.yml file 2025-01-23 19:09:37 +00:00
Simeon Pursiainen f69615437a Edit email.html 2025-01-23 19:06:02 +00:00
Simeon Pursiainen 533f367b4e Edit Hafv date 2025-01-21 21:18:06 +00:00
Simeon Pursiainen 93202d8a11 skip ci 2025-01-21 20:59:13 +00:00
Simeon Pursiainen e1e1adc397 Changed date for 2025 Hafv 2025-01-21 13:15:02 +00:00
tommi s 0df2d4ab46 Merge branch 'develop' into 'master'
Develop to master

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!98
2023-10-05 13:45:16 +00:00
Tommi S 4e0a93631d Compiled translations 2023-10-05 16:20:28 +03:00
Tommi S be9e308587 update kaeyhmy categories and add english kaehmyguide 2023-10-05 14:25:22 +03:00
tommi s 738051355b Merge branch 'develop' into 'master'
fixes

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!97
2023-10-04 18:02:38 +00:00
Tommi S 8550a9a02b prkl 2023-10-04 20:46:06 +03:00
tommi s 1cf67c7686 Merge branch 'develop' into 'master'
Edit signup email contact address

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!96
2023-10-04 17:26:31 +00:00
Tommi S 703bb91bfd Edit signup email contact 2023-10-04 20:04:20 +03:00
tommi s 2ff0cab544 Merge branch 'develop' into 'master'
Kaehmy dates update

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!95
2023-10-03 20:42:29 +00:00
Tommi S 6b4a00ebd8 Edit text 2023-10-03 23:30:34 +03:00
Tommi S 677c1400fa Fix lines 2023-10-03 00:08:08 +03:00
Tommi S a2e08d0ea6 Update kaehmy header dates 2023-10-02 23:23:32 +03:00
tommi s 901f2bed96 Merge branch 'feature/disable_kaehmybot_button' into 'develop'
Add kaehmybot disable button

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!94
2023-10-02 20:05:24 +00:00
Tommi S 6004156b6f Add kaehmybot disable button 2023-10-01 17:34:01 +03:00
Tommi S e00323bffe Update readme docker database name 2023-09-29 21:27:38 +03:00
Tommi S 6ef0dbf91b Update kaehmy dates 2023-09-29 20:10:39 +03:00
tommi s 2169bad90d Merge branch 'develop' into 'master'
Develop to master

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!93
2023-09-15 09:21:19 +00:00
Tommi S 63a4781574 Edit accepting message 2023-09-15 12:02:31 +03:00
Tommi S e735ebe64a Merge branch 'develop' of gitlab.com:sahkoinsinoorikilta/vtmk/web2.0-backend into develop 2023-09-14 17:54:48 +03:00
Tommi S f9db8476a1 Does it work now 2023-09-14 17:52:54 +03:00
tommi s 2a24544056 Update deprecated variables 2023-09-14 14:39:38 +00:00
Tommi S fb7bee5480 Update djangorestframework 2023-09-14 15:33:19 +03:00
Tommi S 8355d10635 Update django, requests, sentry-sdk, sqlparse 2023-09-14 11:35:59 +03:00
Tommi S 21892e277e Update certifi 2023-09-14 11:28:58 +03:00
Tommi S 81e1f994eb Update tg-channel link and remove freshmen groups from accepting email 2023-09-13 19:03:49 +03:00
Aarni Halinen c79d824a8a Merge branch 'develop' into 'master'
Prod deploy: Add log level to gunicorn

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!92
2023-03-27 16:30:59 +00:00
Aarni Halinen fc6e02b71b Add log level to gunicorn 2023-03-27 19:16:08 +03:00
Ilari Ojakorpi 1e14f98f9d Merge branch 'develop' into 'master'
Develop

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!91
2023-03-05 18:03:40 +00:00
Ojakoo 8815ccf667 update django 2023-03-05 19:53:44 +02:00
Ojakoo b694370572 :D 2023-03-05 19:35:51 +02:00
Ilari Ojakorpi f51611bbee Merge branch 'develop' into 'master'
Develop to Master

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!90
2023-02-07 14:58:52 +00:00
Ojakoo fda9acfb2f update certifi 2023-02-07 16:43:02 +02:00
Ojakoo 30c6e4809b update hafv page 2023-02-07 16:35:07 +02:00
Ilari Ojakorpi 4373f37dfe Merge branch 'develop' into 'master'
Merge Develop to master

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!89
2022-12-29 13:53:51 +00:00
Ilari Ojakorpi 39754a5e63 Merge branch 'django-v4' into 'develop'
Draft: Django v4

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!72
2022-12-25 13:10:34 +00:00
Ojakoo 0be3ee69be add ordering to remove pagination error 2022-12-24 21:07:46 +02:00
Ojakoo c6f0f4615b lint 2022-12-24 20:59:07 +02:00
Ojakoo 3ac5400b79 updated pyexcel packages 2022-12-24 20:55:40 +02:00
Ojakoo 429d3a0602 make datetimes aware 2022-12-23 13:16:39 +02:00
Ojakoo 1b086843dc Update poetry 2022-12-21 19:16:55 +02:00
Ojakoo b9280ea026 update protobuf 2022-12-21 18:59:27 +02:00
Ojakoo b36022e546 Update django 2022-12-21 18:57:33 +02:00
Ojakoo 9bb57840a7 Update pillow 2022-12-21 18:55:49 +02:00
Ojakoo 36bd74c6cc Revert some merge stuff 2022-12-21 18:44:43 +02:00
Ojakoo a2615ae27d Merge branch 'develop' into django-v4 2022-12-21 18:34:39 +02:00
Ojakoo f7de9e32d3 remove last_paid column to not search database for unexisting entries 2022-12-21 18:13:54 +02:00
Ojakoo 57d8c4321f Update readme. 2022-12-21 16:20:59 +02:00
Aarni Halinen 632eedea9c Test scheduled job 2022-10-31 22:36:28 +02:00
Aarni Halinen 1405d89d9a Test scheduled job 2022-10-31 22:21:45 +02:00
Aarni Halinen da1ae8d721 Test scheduled job 2022-10-31 22:19:30 +02:00
Aarni Halinen 52a83b9336 Scheduled job for Docker cleanup 2022-10-24 21:58:39 +03:00
Aarni Halinen f67ce55d60 Fix webapp test as module 2022-10-24 21:42:41 +03:00
Ilari Ojakorpi d8fdc2cc74 Merge branch 'develop' into 'master'
Changes to kaehmy categories

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!86
2022-10-05 11:24:00 +00:00
Ojakoo 5e1390ab6b Changes to kaehmy categories 2022-10-05 14:18:28 +03:00
Ilari Ojakorpi eb2ae3368a Merge branch 'develop' into 'master'
Develop to master

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!85
2022-10-05 10:08:08 +00: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
Ilari Ojakorpi f7f63b8670 Merge branch 'develop' into 'master'
Use html templates for kaehmy emails. No translations for now.

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!84
2022-09-23 18:01:33 +00: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
Ilari Ojakorpi 53742e5caa Merge branch 'develop' into 'master'
Update kaehmy page

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!83
2022-09-23 15:29:08 +00: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
Ilari Ojakorpi 2affae7bfd Merge branch 'develop' into 'master'
Develop

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!82
2022-09-19 08:09:20 +00: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
Ilari Ojakorpi f7659e1e1b Merge branch 'develop' into 'master'
Modified paths

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!81
2022-08-17 20:53:54 +00:00
Ojakoo ea9a732803 Modified paths 2022-08-17 23:00:11 +03:00
Ilari Ojakorpi a4ae136e1a Merge branch 'develop' into 'master'
juuh eli

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!80
2022-08-16 20:07:54 +00:00
Ojakoo 0026b788b2 juuh eli 2022-08-16 23:00:36 +03:00
Ilari Ojakorpi 570fff1d7d Merge branch 'develop' into 'master'
Totally works now

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!79
2022-08-16 19:33:24 +00:00
Ojakoo da3a484f6c wbn? 2022-08-16 22:24:20 +03:00
Ilari Ojakorpi 9d116528b9 Merge branch 'develop' into 'master'
Develop

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!78
2022-08-16 18:49:48 +00: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
Ilari Ojakorpi afbdca1501 Merge branch 'develop' into 'master'
Enable GOOGLE_SERVICE_ACCOUNT, should work now..

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!77
2022-08-16 18:14:50 +00:00
Ojakoo 4e59eee200 Enable GOOGLE_SERVICE_ACCOUNT, should work now.. 2022-08-16 20:59:19 +03:00
Aarni Halinen 03e4ccdf5d Merge branch 'develop' into 'master'
Prod deploy: Fix kaehmy form

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!76
2022-08-11 08:36:29 +00:00
Aarni Halinen bb0b2a2628 Fix kaehmy form 2022-08-11 11:28:30 +03:00
Aarni Halinen 16454ebdf6 Merge branch 'develop' into 'master'
Prod deploy: Disable GOOGLE_SERVICE_ACCOUNT for debugging

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!75
2022-08-11 07:00:05 +00:00
Aarni Halinen 32d636d3ee Disable GOOGLE_SERVICE_ACCOUNT for debugging 2022-08-11 09:52:58 +03:00
Ilari Ojakorpi 2e2464fb5f Merge branch 'develop' into 'master'
dumdum

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!74
2022-08-09 18:55:55 +00:00
Ojakoo c91b99cdb1 lol 2022-08-09 21:44:01 +03:00
Ilari Ojakorpi caf2113e49 Merge branch 'develop' into 'master'
Merge 'develop' into' master'

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!71
2022-08-09 14:11:42 +00:00
Aarni Halinen c1a1f6e534 Replace deprecated functions 2022-08-06 16:54:16 +03:00
Aarni Halinen f79d1467f7 Install Django v4 2022-08-06 16:38:28 +03:00
Aarni Halinen 40cf9121b6 Clean-up README and coveragerc 2022-08-06 16:35:58 +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 c45bcc5442 Merge branch 'develop' into 'master'
Prod deploy: Fix Filebrowser authentication cookie

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!66
2022-07-24 18:26:38 +00: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 2383e2089d Merge branch 'develop' into 'master'
Prod deploy: Add algorithms for FileBrowser JWT verification

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!65
2022-07-24 17:35:33 +00: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
Ilari Ojakorpi 5f467323b5 Merge branch 'develop' into 'master'
Merge 'Develop' into master

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!64
2022-07-06 19:50:50 +00: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
Ilari Ojakorpi a9c122c0d4 Merge branch 'develop' into 'master'
heevi email fix

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!62
2022-05-26 09:28:20 +00:00
Ojakoo ec4317d9e7 heevi email fix 2022-05-25 19:13:16 +03:00
Ilari Ojakorpi d4a219290b Merge branch 'develop' into 'master'
Merge develop into master

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!61
2022-05-22 23:08:42 +00: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
Ilari Ojakorpi e74580fdde Merge branch 'develop' into 'master'
Merge develop into master

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!60
2022-05-19 20:58:32 +00: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 8c90471eb1 Merge branch 'develop' into 'master'
Prod deploy: Webhooks (new TG bot setup)

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!55
2022-01-14 00:50:18 +00: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 fc73424665 Merge branch 'develop' into 'master'
Prod deploy: Fix Ohlhafv emails

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!52
2022-01-13 20:12:13 +00: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 b610a8af6e Merge branch 'develop' into 'master'
Prod deploy: Disable sentry performance monitoring

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!51
2021-11-17 17:30:51 +00:00
Aarni Halinen 6380d39afb disable sentry performance monitoring 2021-11-17 19:23:28 +02:00
Toni Lyttinen 6022b11dc1 Merge branch 'develop' into 'master'
Prod deploy: Sentry & update Ohlhafv page

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!49
2021-11-17 16:22:34 +00: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
Oskari Ponkala 72ea31a887 Merge branch 'develop' into 'master'
Develop

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!47
2021-10-05 08:44:31 +00: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 40b824355b Merge branch 'develop' into 'master'
Develop

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!45
2021-06-01 08:45:26 +00: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 5f69f34bf3 Merge branch 'develop' into 'master' 2021-05-16 18:43:23 +03:00
Aarni Halinen fb3328cb23 set DB on sslmode=require on prod 2021-05-16 18:42:58 +03:00
Aarni Halinen c0db047cd9 Merge branch 'develop' into 'master' 2021-05-16 18:18:00 +03:00
Aarni Halinen e2c32e81a7 Merge branch 'develop' into 'master'
Prod deploy: Python 3.9, poetry & dependency updates, SendGrid, Managed DB

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!41
2021-05-16 14:54:43 +00: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
Toni Lyttinen 6a65ca32d7 Merge branch 'develop' into 'master'
Merge feed list order fix to master

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!39
2021-04-08 10:22:38 +00:00
Aarni Halinen 99739cd035 Merge branch 'develop' into 'master'
1st Live deploy

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!37
2021-03-31 21:02:11 +00:00
Aarni Halinen 8454e67a92 Merge branch 'develop' into 'master'
Prod deploy

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!36
2021-03-30 17:08:29 +00:00
Aarni Halinen 33c7c20140 Merge branch 'develop' into 'master'
Prod deploy

* Soft deletes

* sahkoinsinoorikilta.fi domain

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!34
2021-01-19 15:42:22 +00:00
Aarni Halinen 5a77b1546d Merge branch 'develop' into 'master'
Azure deploy & Signup flow

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!32
2020-12-12 12:11:09 +00:00
Toni Lyttinen 81971b6da4 Merge branch 'develop' into 'master'
added "toimikunta-appro" date to kaehmy.html

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!28
2020-10-28 13:39:45 +00:00
Toni Lyttinen a68fa9a6d6 Merge branch 'develop' into 'master'
Kaehmy fix Merge

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!27
2020-10-20 18:43:07 +00:00
Toni Lyttinen 034e04a788 Merge branch 'develop' into 'master'
Merge kaehmy changes to master

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!26
2020-10-07 08:48:06 +00:00
Aarni Halinen d416fda39e Merge branch 'develop' into 'master'
SIK100 Seminar email hack

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!23
2020-08-31 01:11:31 +00:00
Aarni Halinen e4fbb58026 Merge branch 'develop' into 'master'
API serializers for signups

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!22
2020-08-29 12:09:55 +00:00
Aarni Halinen 56dfd57698 Merge branch 'develop' into 'master'
Serialize SignupForm visible

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!20
2020-07-25 09:23:14 +00:00
Aarni Halinen 8632aa01da Merge branch 'develop' into 'master'
Signup modifications to production

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!19
2020-07-24 19:29:20 +00:00
Aarni Halinen 8b1f668d38 Merge branch 'develop' into 'master'
Dependency bump, move volumes under static file server

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!17
2020-03-05 19:21:53 +00:00
Toni Lyttinen 027bf5e7bd Merge branch 'develop' into 'master'
Color updates to Ohlhalv

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!16
2020-02-02 14:42:40 +00:00
Toni Lyttinen be22fea3f2 Merge branch 'develop' into 'master'
Updated ohlhafv challenge buttoncolor to match the page background

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!15
2020-02-02 14:26:23 +00:00
Toni Lyttinen 5e434408b0 Merge branch 'develop' into 'master'
New Ohlhafv graphics

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!14
2020-02-02 14:00:17 +00:00
Toni Lyttinen 41762e920f Updated background color to match image 2020-02-02 13:10:41 +00:00
Aarni Halinen f390e1fb1f Merge branch 'develop' into 'master'
api.sika.sik.party

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!13
2019-12-18 18:39:27 +00:00
Aarni Halinen 4e35a73a4b Merge branch 'develop' into 'master'
Update admin login url

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!12
2019-12-17 19:44:45 +00:00
Aarni Halinen 6955659acb Merge branch 'develop' into 'master'
Add filebrowser

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!11
2019-12-17 17:24:04 +00:00
Aarni Halinen dbc7811651 Merge branch 'develop' into 'master'
Update PG, Hackathon commits

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!10
2019-12-17 16:39:12 +00:00
Aarni Halinen 6c4a26eb44 Merge branch 'develop' 2019-10-10 01:05:24 +03:00
Aarni Halinen fb9dbe2cd2 Merge branch 'develop' 2019-10-10 00:46:11 +03:00
Aarni Halinen b346c2122a Merge branch 'develop' into 'master'
Python 3.7 and path fixes

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!7
2019-10-09 21:11:12 +00:00
Aarni Halinen c9c814e405 Merge branch 'develop' 2019-10-09 22:21:57 +03:00
Aarni Halinen 0f72a7ea21 Merge branch 'develop' 2019-10-09 21:18:16 +03:00
Aarni Halinen 135c0309b5 Merge branch 'develop' 2019-10-09 19:33:45 +03:00
Aarni Halinen bd4551a222 Merge branch 'develop' 2019-10-09 19:03:50 +03:00
Aarni Halinen 7fee47f150 Merge branch 'develop' 2019-10-08 23:16:19 +03:00
Aarni Halinen b0dd95fa86 Merge branch 'develop' 2019-10-08 22:17:28 +03:00
Aarni Halinen 9516dbbbea Merge branch 'develop' 2019-10-08 20:55:09 +03:00
Aarni Halinen 5b2546217c Merge branch 'develop' 2019-10-08 19:57:06 +03:00
Aarni Halinen 2646914b8a Merge branch 'develop' 2019-10-08 19:43:01 +03:00
Aarni Halinen 17c00e519b Merge branch 'develop' 2019-10-08 13:04:42 +03:00
Aarni Halinen 389771d106 Merge branch 'develop' 2019-10-08 12:41:51 +03:00
Aarni Halinen ed4e226186 Merge branch 'develop' 2019-10-08 10:19:30 +03:00
Aarni Halinen bc7a8ebd17 Merge branch 'develop' 2019-10-08 10:01:29 +03:00
Aarni Halinen cfdac40d3b Merge branch 'develop' 2019-10-08 09:41:07 +03:00
Aarni Halinen ec4be22552 Merge branch 'develop' 2019-10-08 01:00:41 +03:00
Aarni Halinen 06d5e3c6f4 Merge branch 'develop' 2019-10-08 00:46:46 +03:00
Aarni Halinen 89cd91dbad Merge branch 'develop' 2019-10-08 00:32:54 +03:00
Aarni Halinen 44c091f2d5 Merge branch 'develop' 2019-10-08 00:05:15 +03:00
Aarni Halinen c1b3bedf8d Merge branch 'develop' 2019-10-07 23:53:24 +03:00
Aarni Halinen 3c16029e88 Merge branch 'develop' 2019-10-07 22:03:17 +03:00
Aarni Halinen 41d6f17716 Merge branch 'develop' 2019-10-07 21:28:27 +03:00
Aarni Halinen 2649afdd4b Merge branch 'develop' 2019-10-07 21:19:28 +03:00
Aarni Halinen 8ee514326a Merge branch 'develop' 2019-10-07 21:10:43 +03:00
Aarni Halinen 48e34ece4f Merge branch 'develop' 2019-10-07 20:31:48 +03:00
Aarni Halinen d2af34bf0c Merge branch 'develop' 2019-10-07 19:59:34 +03:00
Aarni Halinen ddf4ad7b8d Merge branch 'develop' 2019-10-07 19:57:32 +03:00
Aarni Halinen 3169191a0d Merge branch 'develop' into 'master'
New Sika & Membership application

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!3
2019-10-07 16:04:32 +00:00
318 changed files with 8812 additions and 9280 deletions
-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
+9 -6
View File
@@ -1,10 +1,13 @@
HOST=api.dev.sahkoinsinoorikilta.fi
DEPLOY_ENV=local
SENTRY_DSN=
HOST=localhost
DEBUG=True
SECRET_KEY=7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp(
TG_BOT_TOKEN=
EMAIL_HOST=
EMAIL_PASSWD=
DB_NAME=postgres
DB_USER=postgres
DB_PASSWD=postgres
DB_HOST=db
DB_PORT=5432
DB_HOST=localhost
DB_PORT=5432
EMAIL_API_KEY=
GROUP_KEY=
GOOGLE_CREDS='{}'
+7 -4
View File
@@ -1,10 +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(
TG_BOT_TOKEN=
EMAIL_HOST=
EMAIL_PASSWD=
DB_NAME=postgres
DB_USER=postgres
DB_PASSWD=postgres
DB_HOST=db
DB_PORT=5432
DB_PORT=5432
EMAIL_API_KEY=
GROUP_KEY=
GOOGLE_CREDS='{}'
+7 -16
View File
@@ -1,23 +1,14 @@
*.swp
.DS_Store
.env
*~
*.pyc
*.sqlite3
uwsgi.ini
uwsgi.log
members/logs/*
.idea/
logs/
/collected_static/
/media/
logs/
members/logs/*
node_modules/
.coverage
db.sqlite3
requirements_henu.txt
/collected_static/
mydatabase
settings.json
.vscode/
.DS_Store
.idea/
*.code-workspace
sik_test
venv/
venv/
.venv/
+86 -12
View File
@@ -1,13 +1,17 @@
stages:
- setup
- audit
- lint
- test
- publish
- deploy
- cleanup
install:
image: node:14
stage: setup
only:
- pushes
script:
- npm ci
artifacts:
@@ -15,9 +19,27 @@ install:
- node_modules
expire_in: 1 week
audit:
image: python:3.9
stage: audit
only:
- pushes
- develop
except:
- master
needs: []
before_script:
- pip install poetry==1.3.1
- poetry config virtualenvs.create false
- poetry install --no-interaction --no-ansi
script:
- safety check
test:
image: python:3.7
image: python:3.9
stage: test
only:
- pushes
needs: []
services:
- postgres:12
@@ -27,24 +49,30 @@ test:
POSTGRES_PASSWORD: postgres
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
DB_HOST: postgres
before_script:
- pip install poetry==1.3.1
- poetry config virtualenvs.create false
- poetry install --no-interaction --no-ansi
script:
- python -V
- pip install -r requirements.txt
- python manage.py migrate --noinput
- python manage.py createdefaultadmin
- python manage.py test
lint:py:
image: python:3.7
image: python:3.9
stage: lint
only:
- pushes
needs: []
script:
- pip install pycodestyle
- pycodestyle --config=pycodestyle.cfg --count .
- pip install black==22.3.0
- black --check .
lint:js:
image: node:14
stage: lint
only:
- pushes
needs: ["install"]
script:
- npm run lint:js
@@ -52,13 +80,15 @@ lint:js:
lint:md:
image: node:14
stage: lint
only:
- pushes
needs: ["install"]
script:
- npm run lint:md
publish:
stage: publish
image: docker:stable
stage: publish
needs: ["test", "lint:py", "lint:js", "lint:md"]
services:
- docker:stable-dind
@@ -72,8 +102,8 @@ publish:
- docker push "$IMAGE_NAME"
deploy:dev:
stage: deploy
image: docker:stable
stage: deploy
only:
- develop
environment:
@@ -87,7 +117,7 @@ deploy:dev:
- 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"
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
script:
- docker stack deploy --with-registry-auth -c stack-compose-dev.yml "$SERVICE_NAME"
after_script:
@@ -100,7 +130,7 @@ deploy:production:
- master
environment:
name: production
url: api.sahkoinsinoorikilta.fi
url: https://api.sahkoinsinoorikilta.fi
when: manual
variables:
DOCKER_HOST: $CI_DOCKER_HOST
@@ -110,8 +140,52 @@ deploy:production:
- 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"
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
script:
- docker stack deploy --with-registry-auth -c stack-compose.yml "$SERVICE_NAME"
after_script:
- docker logout "$CI_REGISTRY"
- docker logout "$CI_REGISTRY"
docker_prune:dev:
image: docker:stable
stage: cleanup
only:
- schedules
environment:
name: dev
url: http://api.dev.sahkoinsinoorikilta.fi
variables:
DOCKER_HOST: $DEV_CI_DOCKER_HOST
DOCKER_TLS_VERIFY: 1
before_script:
- mkdir -p ~/.docker
- echo "$DEV_TLSCACERT" > ~/.docker/ca.pem
- echo "$DEV_TLSCERT" > ~/.docker/cert.pem
- echo "$DEV_TLSKEY" > ~/.docker/key.pem
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
script:
- docker system prune
after_script:
- docker logout "$CI_REGISTRY"
docker_prune:prod:
image: docker:stable
stage: cleanup
only:
- schedules
environment:
name: production
url: https://api.sahkoinsinoorikilta.fi
variables:
DOCKER_HOST: $CI_DOCKER_HOST
DOCKER_TLS_VERIFY: 1
before_script:
- mkdir -p ~/.docker
- echo "$TLSCACERT" > ~/.docker/ca.pem
- echo "$TLSCERT" > ~/.docker/cert.pem
- echo "$TLSKEY" > ~/.docker/key.pem
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
script:
- docker system prune
after_script:
- docker logout "$CI_REGISTRY"
+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
+1 -1
View File
@@ -4,7 +4,7 @@
PURPLE='\033[0;35m'
NC='\033[0m' # No Color
source "${VIRTUAL_ENV}/bin/activate"
. "${VIRTUAL_ENV}/bin/activate"
if [ $? -ne 0 ]
then
+1 -1
View File
@@ -1 +1 @@
3.7.4
3.9
-1
View File
@@ -1 +0,0 @@
global_static
+22 -14
View File
@@ -1,20 +1,28 @@
FROM python:3.7-alpine
FROM python:3.9-slim-buster as builder
ENV PYTHONUNBUFFERED 1
WORKDIR /app
COPY requirements.txt ./
COPY requirements.production.txt ./
COPY . ./
# uWSGI, gunicorn etc.
RUN apk add --no-cache python3-dev build-base linux-headers pcre-dev openssl bash \
# PSQL
&& apk add --no-cache postgresql-dev \
# Pillow
&& apk add --no-cache jpeg-dev zlib-dev \
&& pip install --upgrade pip \
&& pip install -r requirements.txt \
&& pip install -r requirements.production.txt
ENV POETRY_VERSION=1.3.1
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"]
+153
View File
@@ -0,0 +1,153 @@
# Web 2.0 Backend
[Django](https://www.djangoproject.com/) backend containing multiple small applications and api for Next.js frontend.
* **Web app:** Backend for the main website.
* **Member register:** Data table app for viewing and modifying the member register, member applications and membership payments.
* **Kaehmy:** Form for creating and listing kaehmys
* **Ohlhafv:** Form for creating and listing ohlhafv challenges.
* **Infoscreen:** Angular-based slideshow app for the guild room's screens.
## Installation
Set up your SSH key authentication in GitLab Profile Settings. Then clone the repository and checkout the development branch:
```bash
git clone git@gitlab.com:sahkoinsinoorikilta/vtmk/web2.0-backend.git
cd web2.0-backend
git checkout develop
```
Copy env file for local use:
```bash
cp .env.dev .env
```
### Poetry
For depedencies and virtual environment, we use [poetry](https://python-poetry.org/).
First install [python](https://wiki.python.org/moin/BeginnersGuide/Download). Then install poetry:
```bash
python3 -m pip install poetry==1.3.1
```
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
```
### Node
We use Node.js for few development tasks, like linting. Easiest way to install Node is [nvm](https://github.com/nvm-sh/nvm). After installing install dependencies:
```
npm install
```
TODO: List scripts
### Database
To run a local development database **[docker](https://docs.docker.com/engine/install/)** is recommended. If you want to additianally use a db management tool **[pgAdmin](https://www.pgadmin.org/download/)** is nice.
After installing docker use the following to create a database:
```bash
docker run --name sik.web.db -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres:12
```
## Development
Activate virtual environment in shell
```bash
python3 -m poetry shell
```
Install dependencies
```bash
poetry install
```
### Initializing data
Run the following `manage.py` commands to initialize a new database. Do not run these in production without thinking!
```bash
python manage.py migrate # run migrations
python manage.py createdefaultadmin # creates an admin user
python manage.py initialize # creates user groups
python manage.py createdummydata # creates dummy members to the member register
```
### Running
```bash
python manage.py runserver
```
Visit [https://localhost:8000](https://localhost:8000) in your browser!
Using address `0.0.0.0` will bind to all IP addresses. Using `localhost` will only bind to your machine.
```bash
python manage.py runserver 0.0.0.0:8000
```
### Development workflow
When you start working on a feature, create a feature branch for your changes. These feature branches should be prefixed with `feature`.
Example of creating a feature branch:
```bash
git checkout -b feature-branch-name
```
When your changes are ready and the code works without errors, submit a merge request to `develop` in GitLab. Another developer reviews your changes and runs the merge. Feature branches should be closed on merge.
Bugfixes do not need their own feature branches and can be pushed straight to `develop`, but if the fix needs a notable amount of work, it should be done in a `bugfix` branch instead.
Merge requests to `master` should be reviewed by multiple developers. Only a moderator can accept merge requests to `master`.
### Linting
Lint python files using `black` with
```bash
npm run lint:py # check changes
npm run lint:py:fix # fix errors
```
Lint javascript and markdown using `eslint` and `remark` with
```bash
npm run lint:md # markdown
npm run lint:js # javascript
```
Use an editor with linting capabilities to write pretty code that passes linting. Examples include _VSCode_, _Atom_ and _Pycharm_.
### Unit tests
Run unit tests with
```bash
python manage.py test
```
Due to the mostly static nature of the project, most elements are difficult to properly unit test. If you write code with actual logic, make sure to write at least one unit or integration test that tests your code's core functionality.
Tests are located in `tests.py` under every subproject.
## Production
Project is run in production with Docker. See `Dockerfile` for details.
For more information about deployment check **[infra](https://gitlab.com/sahkoinsinoorikilta/vtmk/infra)** repository.
## GitLab CI
All pushed changes go through the GitLab Continuous Integration, which consists of automated unit testing and linting. Make sure your changes pass both before merging to `develop` or `master`.
-1
View File
@@ -13,7 +13,6 @@ services:
web:
build: .
image: registry.gitlab.com/sahkoinsinoorikilta/vtmk/web2.0-backend
command: ["bash", "-c", "cd /app & bash setup.sh --no-input --no-npm && gunicorn -w 4 -b 0.0.0.0:8000 sikweb.wsgi"]
env_file:
- .env
ports:
+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),
),
]
+52 -54
View File
@@ -7,7 +7,7 @@ from django import forms
from django.utils import timezone
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
class InfoItem(models.Model):
@@ -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>
+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)
+24 -23
View File
@@ -1,6 +1,6 @@
"""File containing infoscreen urls."""
from django.conf.urls import url
from django.urls import re_path
from django.conf import settings
from infoscreen.views import index
@@ -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),
re_path(r"^$", default),
re_path(r"^admin$", admin),
re_path(r"^(?P<idx>\d+)$", index),
re_path(r"^items$", info_items),
re_path(r"^rotation/(?P<idx>\d+)$", rotation),
re_path(r"^rotations$", rotations),
re_path(r"^instance$", createInstance),
re_path(r"^instance/(?P<idx>\d+)$", deleteInstance),
re_path(r"^types$", info_types),
re_path(r"^delete_item/(?P<type_id>\d+)/(?P<idx>\d+)$", delete_info_item),
re_path(r"^create_external_image$", createExternalImageInfoItem),
re_path(r"^create_image$", create_image_item),
re_path(r"^create_video$", create_video_item),
re_path(r"^create_abbitem$", createABBItem),
re_path(r"^create_sossoitem$", createSossoItem),
re_path(r"^create_lunchitem$", createLunchItem),
re_path(r"^create_eventitem$", createEventItem),
re_path(r"^create_apyitem$", createApyItem),
re_path(r"^create_websiteitem$", createExternalWebsiteItem),
re_path(r"^create_rotation$", create_rotation),
re_path(r"^delete_rotation/(?P<id>\d+)$", delete_rotation),
re_path(r"^apyjson", get_apy_json),
]
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"
+55 -30
View File
@@ -1,17 +1,21 @@
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_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",
),
]
+82 -84
View File
@@ -1,70 +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
from webapp.models import BaseRole
import logging
from django.utils.translation import gettext_lazy as _
VERBOSE_NAME = _("Kaehmy")
# TODO: Move BaseRole to Kaehmt App; will fuck up the DB since table is removed, if no data migration is done before-hand.
# Either reconstruct all kaehmy roles from scratch then, or do these migrations:
# 1. Create table here
# 2. Data migrate from webapp BaseRole to new kaehmy BaseRole
# 3. Delete webapp BaseRole table
class BaseRole(models.Model):
"""Base model for occupations/roles."""
VERBOSE_NAME = _('Kaehmy')
class KaehmyBaseRole(BaseRole):
"""ABC"""
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")),
("siwa", _("SIK's free time")),
("media", _("Media")),
("tech", _("Technology")),
("wellbeing", _("Wellbeing")),
("sikpaja", _("Sik-paja")),
("ceremonies", _("Ceremonies")),
("studies", _("Studies")),
("sosso", _("Sössö magazine")),
("pota", _("PoTa")),
("alumni", _("Alumni relations")),
("n", _("N")),
("others", _("Others")),
)
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):
@@ -75,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):
@@ -88,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."""
@@ -137,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: #0c2938;
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: 70 KiB

After

Width:  |  Height:  |  Size: 88 KiB

+9 -3
View File
@@ -1,6 +1,6 @@
import django_tables2 as tables
from django.db.models import Count, Q
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from kaehmy.models import Application
@@ -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)
-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/kaehmy/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 = ()
+9 -8
View File
@@ -1,8 +1,8 @@
"""Kaehmy urls."""
from django.conf.urls import url
from django.urls import re_path
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from kaehmy.views import view
from kaehmy.views import list_view
@@ -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),
re_path(r"^new", view),
re_path(r"^submit", submit),
re_path(r"^add_comment", comment),
re_path(r"^statistics", statistics_view),
re_path(r"^export", export_view),
re_path(r"^$", list_view),
]
if settings.DEBUG:
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
urlpatterns += staticfiles_urlpatterns()
+74 -77
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.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 = (f'{name.capitalize()} on vastannut kaehmyhakemukseesi tai kommenttiisi kaehmypalvelussa.\r\n\r\n'
'Käy lukemassa viesti osoitteessa https://{URL}/kaehmy')
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(
f'Sent kaehmy comment email to recipient <{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,46 @@ 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")
kaehmybot_allowed = form.cleaned_data.get("kaehmybot") == "1"
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 = f'https://{URL}/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 +169,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"
+26 -23
View File
@@ -2,7 +2,7 @@
from django import forms
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from members.models import Member, Payment, Request
@@ -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):
+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)),
),
]
+53 -29
View File
@@ -11,57 +11,81 @@ import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('members', '0002_auto_20170329_1857'),
("members", "0002_auto_20170329_1857"),
]
operations = [
migrations.CreateModel(
name='Payment',
name="Payment",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateTimeField(default=datetime.datetime(1970, 1, 1, 2, 0))),
('source', models.CharField(max_length=255)),
('first_name', models.CharField(max_length=255)),
('last_name', models.CharField(max_length=255)),
('email', models.EmailField(max_length=255)),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"date",
models.DateTimeField(default=datetime.datetime(1970, 1, 1, 2, 0)),
),
("source", models.CharField(max_length=255)),
("first_name", models.CharField(max_length=255)),
("last_name", models.CharField(max_length=255)),
("email", models.EmailField(max_length=255)),
],
),
migrations.CreateModel(
name='Request',
name="Request",
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(default='ei_tiedossa', max_length=255)),
('AYY', models.BooleanField(default=False)),
('jas', models.BooleanField(default=False)),
('submitted', models.DateTimeField(default=django.utils.timezone.now)),
(
"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(default="ei_tiedossa", max_length=255)),
("AYY", models.BooleanField(default=False)),
("jas", models.BooleanField(default=False)),
("submitted", models.DateTimeField(default=django.utils.timezone.now)),
],
options={
'abstract': False,
"abstract": False,
},
),
migrations.RemoveField(
model_name='memberrequest',
name='member',
model_name="memberrequest",
name="member",
),
migrations.AlterField(
model_name='member',
name='POR',
field=models.CharField(default='ei_tiedossa', max_length=255),
model_name="member",
name="POR",
field=models.CharField(default="ei_tiedossa", max_length=255),
),
migrations.AlterField(
model_name='member',
name='paid',
model_name="member",
name="paid",
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.DeleteModel(
name='MemberRequest',
name="MemberRequest",
),
migrations.AddField(
model_name='payment',
name='member',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='members.Member'),
model_name="payment",
name="member",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="members.Member",
),
),
]
+43 -43
View File
@@ -8,80 +8,80 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0003_auto_20170329_1928'),
("members", "0003_auto_20170329_1928"),
]
operations = [
migrations.RemoveField(
model_name='payment',
name='email',
model_name="payment",
name="email",
),
migrations.RemoveField(
model_name='payment',
name='first_name',
model_name="payment",
name="first_name",
),
migrations.RemoveField(
model_name='payment',
name='last_name',
model_name="payment",
name="last_name",
),
migrations.AlterField(
model_name='member',
name='AYY',
field=models.BooleanField(default=False, verbose_name='AYY'),
model_name="member",
name="AYY",
field=models.BooleanField(default=False, verbose_name="AYY"),
),
migrations.AlterField(
model_name='member',
name='POR',
field=models.CharField(max_length=255, verbose_name='Place of residence'),
model_name="member",
name="POR",
field=models.CharField(max_length=255, verbose_name="Place of residence"),
),
migrations.AlterField(
model_name='member',
name='email',
field=models.EmailField(max_length=254, verbose_name='Email'),
model_name="member",
name="email",
field=models.EmailField(max_length=254, verbose_name="Email"),
),
migrations.AlterField(
model_name='member',
name='first_name',
field=models.CharField(max_length=127, verbose_name='First name'),
model_name="member",
name="first_name",
field=models.CharField(max_length=127, verbose_name="First name"),
),
migrations.AlterField(
model_name='member',
name='jas',
field=models.BooleanField(default=False, verbose_name='JAS'),
model_name="member",
name="jas",
field=models.BooleanField(default=False, verbose_name="JAS"),
),
migrations.AlterField(
model_name='member',
name='last_name',
field=models.CharField(max_length=127, verbose_name='Last name'),
model_name="member",
name="last_name",
field=models.CharField(max_length=127, verbose_name="Last name"),
),
migrations.AlterField(
model_name='request',
name='AYY',
field=models.BooleanField(default=False, verbose_name='AYY'),
model_name="request",
name="AYY",
field=models.BooleanField(default=False, verbose_name="AYY"),
),
migrations.AlterField(
model_name='request',
name='POR',
field=models.CharField(max_length=255, verbose_name='Place of residence'),
model_name="request",
name="POR",
field=models.CharField(max_length=255, verbose_name="Place of residence"),
),
migrations.AlterField(
model_name='request',
name='email',
field=models.EmailField(max_length=254, verbose_name='Email'),
model_name="request",
name="email",
field=models.EmailField(max_length=254, verbose_name="Email"),
),
migrations.AlterField(
model_name='request',
name='first_name',
field=models.CharField(max_length=127, verbose_name='First name'),
model_name="request",
name="first_name",
field=models.CharField(max_length=127, verbose_name="First name"),
),
migrations.AlterField(
model_name='request',
name='jas',
field=models.BooleanField(default=False, verbose_name='JAS'),
model_name="request",
name="jas",
field=models.BooleanField(default=False, verbose_name="JAS"),
),
migrations.AlterField(
model_name='request',
name='last_name',
field=models.CharField(max_length=127, verbose_name='Last name'),
model_name="request",
name="last_name",
field=models.CharField(max_length=127, verbose_name="Last name"),
),
]
+18 -9
View File
@@ -9,22 +9,31 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0004_auto_20170512_1454'),
("members", "0004_auto_20170512_1454"),
]
operations = [
migrations.RemoveField(
model_name='member',
name='paid',
model_name="member",
name="paid",
),
migrations.AlterField(
model_name='payment',
name='date',
field=models.DateTimeField(default=datetime.datetime(2017, 5, 13, 10, 29, 50, 116064)),
model_name="payment",
name="date",
field=models.DateTimeField(
default=datetime.datetime(2017, 5, 13, 10, 29, 50, 116064)
),
),
migrations.AlterField(
model_name='payment',
name='source',
field=models.CharField(choices=[('AYY', 'AYY'), ('cash', 'Cash'), ('bank_transfer', 'Bank transfer')], max_length=255),
model_name="payment",
name="source",
field=models.CharField(
choices=[
("AYY", "AYY"),
("cash", "Cash"),
("bank_transfer", "Bank transfer"),
],
max_length=255,
),
),
]
@@ -9,13 +9,15 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0005_auto_20170513_1029'),
("members", "0005_auto_20170513_1029"),
]
operations = [
migrations.AlterField(
model_name='payment',
name='date',
field=models.DateTimeField(default=datetime.datetime(2017, 5, 17, 13, 9, 21, 49238)),
model_name="payment",
name="date",
field=models.DateTimeField(
default=datetime.datetime(2017, 5, 17, 13, 9, 21, 49238)
),
),
]
@@ -9,13 +9,15 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0006_auto_20170517_1309'),
("members", "0006_auto_20170517_1309"),
]
operations = [
migrations.AlterField(
model_name='payment',
name='date',
field=models.DateTimeField(default=datetime.datetime(2017, 5, 18, 15, 38, 7, 668612)),
model_name="payment",
name="date",
field=models.DateTimeField(
default=datetime.datetime(2017, 5, 18, 15, 38, 7, 668612)
),
),
]
@@ -9,18 +9,18 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0007_auto_20170518_1538'),
("members", "0007_auto_20170518_1538"),
]
operations = [
migrations.AlterField(
model_name='member',
name='created',
model_name="member",
name="created",
field=models.DateTimeField(default=datetime.datetime.now),
),
migrations.AlterField(
model_name='payment',
name='date',
model_name="payment",
name="date",
field=models.DateTimeField(default=datetime.datetime.now),
),
]
@@ -9,13 +9,15 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0008_auto_20170518_1540'),
("members", "0008_auto_20170518_1540"),
]
operations = [
migrations.AlterField(
model_name='member',
name='created',
field=models.DateTimeField(default=datetime.datetime.now, verbose_name='Created'),
model_name="member",
name="created",
field=models.DateTimeField(
default=datetime.datetime.now, verbose_name="Created"
),
),
]
+17 -7
View File
@@ -9,18 +9,28 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0009_auto_20170526_1903'),
("members", "0009_auto_20170526_1903"),
]
operations = [
migrations.AlterField(
model_name='payment',
name='date',
field=models.DateTimeField(default=datetime.datetime.now, verbose_name='Date'),
model_name="payment",
name="date",
field=models.DateTimeField(
default=datetime.datetime.now, verbose_name="Date"
),
),
migrations.AlterField(
model_name='payment',
name='source',
field=models.CharField(choices=[('AYY', 'AYY'), ('cash', 'Cash'), ('bank_transfer', 'Bank transfer')], max_length=255, verbose_name='Source'),
model_name="payment",
name="source",
field=models.CharField(
choices=[
("AYY", "AYY"),
("cash", "Cash"),
("bank_transfer", "Bank transfer"),
],
max_length=255,
verbose_name="Source",
),
),
]
@@ -9,13 +9,15 @@ import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('members', '0010_auto_20170526_1910'),
("members", "0010_auto_20170526_1910"),
]
operations = [
migrations.AlterField(
model_name='request',
name='submitted',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Submitted'),
model_name="request",
name="submitted",
field=models.DateTimeField(
default=django.utils.timezone.now, verbose_name="Submitted"
),
),
]
+27 -5
View File
@@ -9,16 +9,38 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('members', '0011_auto_20170526_2013'),
("members", "0011_auto_20170526_2013"),
]
operations = [
migrations.CreateModel(
name='MemberConflict',
name="MemberConflict",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_member', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='memberconflict_first_member', to='members.Member')),
('second_member', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='memberconflict_second_member', to='members.Member')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"first_member",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="memberconflict_first_member",
to="members.Member",
),
),
(
"second_member",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="memberconflict_second_member",
to="members.Member",
),
),
],
),
]
+10 -4
View File
@@ -9,13 +9,19 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('members', '0012_memberconflict'),
("members", "0012_memberconflict"),
]
operations = [
migrations.AlterField(
model_name='payment',
name='member',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='payments', to='members.Member'),
model_name="payment",
name="member",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="payments",
to="members.Member",
),
),
]
@@ -8,18 +8,18 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0013_auto_20170601_1822'),
("members", "0013_auto_20170601_1822"),
]
operations = [
migrations.AlterField(
model_name='member',
name='email',
field=models.EmailField(max_length=254, unique=True, verbose_name='Email'),
model_name="member",
name="email",
field=models.EmailField(max_length=254, unique=True, verbose_name="Email"),
),
migrations.AlterField(
model_name='request',
name='email',
field=models.EmailField(max_length=254, unique=True, verbose_name='Email'),
model_name="request",
name="email",
field=models.EmailField(max_length=254, unique=True, verbose_name="Email"),
),
]
@@ -8,19 +8,19 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('members', '0014_auto_20170920_1457'),
("members", "0014_auto_20170920_1457"),
]
operations = [
migrations.RemoveField(
model_name='memberconflict',
name='first_member',
model_name="memberconflict",
name="first_member",
),
migrations.RemoveField(
model_name='memberconflict',
name='second_member',
model_name="memberconflict",
name="second_member",
),
migrations.DeleteModel(
name='MemberConflict',
name="MemberConflict",
),
]
+11 -7
View File
@@ -9,18 +9,22 @@ import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('members', '0015_auto_20170925_1917'),
("members", "0015_auto_20170925_1917"),
]
operations = [
migrations.AlterField(
model_name='member',
name='created',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Created'),
model_name="member",
name="created",
field=models.DateTimeField(
default=django.utils.timezone.now, verbose_name="Created"
),
),
migrations.AlterField(
model_name='payment',
name='date',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Date'),
model_name="payment",
name="date",
field=models.DateTimeField(
default=django.utils.timezone.now, verbose_name="Date"
),
),
]
@@ -8,12 +8,16 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('members', '0016_auto_20170925_1924'),
("members", "0016_auto_20170925_1924"),
]
operations = [
migrations.AlterModelOptions(
name='member',
options={'permissions': (('check_by_email', 'Can check if user exists by email'),)},
name="member",
options={
"permissions": (
("check_by_email", "Can check if user exists by email"),
)
},
),
]
+16 -7
View File
@@ -8,20 +8,29 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('members', '0017_auto_20170926_1316'),
("members", "0017_auto_20170926_1316"),
]
operations = [
migrations.AlterModelOptions(
name='member',
options={'permissions': (('check_by_email', 'Can check if user exists by email'), ('read_member', 'Can see member in list'))},
name="member",
options={
"permissions": (
("check_by_email", "Can check if user exists by email"),
("read_member", "Can see member in list"),
)
},
),
migrations.AlterModelOptions(
name='payment',
options={'permissions': (('read_payment', 'Can see payment in list'),)},
name="payment",
options={"permissions": (("read_payment", "Can see payment in list"),)},
),
migrations.AlterModelOptions(
name='request',
options={'permissions': (('read_application', 'Can see member application in list'),)},
name="request",
options={
"permissions": (
("read_application", "Can see member application in list"),
)
},
),
]
+10 -3
View File
@@ -8,12 +8,19 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('members', '0018_auto_20170927_1918'),
("members", "0018_auto_20170927_1918"),
]
operations = [
migrations.AlterModelOptions(
name='member',
options={'permissions': (('check_by_email', 'Can check if user exists by email'), ('read_member', 'Can see member in list')), 'verbose_name': 'Member', 'verbose_name_plural': 'Members'},
name="member",
options={
"permissions": (
("check_by_email", "Can check if user exists by email"),
("read_member", "Can see member in list"),
),
"verbose_name": "Member",
"verbose_name_plural": "Members",
},
),
]
@@ -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 = [
("members", "0019_auto_20171029_1143"),
]
operations = [
migrations.AlterField(
model_name="payment",
name="id",
field=models.AutoField(primary_key=True, serialize=False),
),
]
@@ -0,0 +1,23 @@
# Generated by Django 3.2.14 on 2022-08-01 19:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("members", "0020_alter_payment_id"),
]
operations = [
migrations.AlterField(
model_name="member",
name="id",
field=models.AutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name="request",
name="id",
field=models.AutoField(primary_key=True, serialize=False),
),
]
+39 -31
View File
@@ -2,18 +2,20 @@
from django.db import models
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from django.db.models import Q, OuterRef, Subquery
class BaseMember(models.Model):
"""Abstract base model for member."""
id = models.AutoField(primary_key=True)
first_name = models.CharField(_("First name"), max_length=127)
last_name = models.CharField(_("Last name"), max_length=127)
email = models.EmailField(_("Email"), unique=True)
POR = models.CharField(_("Place of residence"),
max_length=255) # place of residence
POR = models.CharField(
_("Place of residence"), max_length=255
) # place of residence
AYY = models.BooleanField(_("AYY"), default=False)
jas = models.BooleanField(_("JAS"), default=False)
@@ -34,7 +36,7 @@ class BaseMember(models.Model):
self.email,
self.POR,
int(self.AYY),
int(self.jas)
int(self.jas),
]
@@ -42,11 +44,9 @@ class Request(BaseMember):
"""Member request model represents one member request."""
class Meta:
permissions = (
('read_application', 'Can see member application in list'),
)
permissions = (("read_application", "Can see member application in list"),)
submitted = models.DateTimeField(_('Submitted'), default=timezone.now)
submitted = models.DateTimeField(_("Submitted"), default=timezone.now)
def to_member(self):
"""Convert array to member model."""
@@ -59,47 +59,55 @@ class Payment(models.Model):
"""Payment model representing one payment event."""
class Meta:
permissions = (
('read_payment', 'Can see payment in list'),
)
permissions = (("read_payment", "Can see payment in list"),)
date = models.DateTimeField(_('Date'), default=timezone.now)
source = models.CharField(_('Source'), choices=[
('AYY', _('AYY')),
('cash', _('Cash')),
('bank_transfer', _('Bank transfer')),
], max_length=255)
id = models.AutoField(primary_key=True)
date = models.DateTimeField(_("Date"), default=timezone.now)
source = models.CharField(
_("Source"),
choices=[
("AYY", _("AYY")),
("cash", _("Cash")),
("bank_transfer", _("Bank transfer")),
],
max_length=255,
)
member = models.ForeignKey('Member',
on_delete=models.PROTECT,
blank=True,
null=True,
related_name='payments')
member = models.ForeignKey(
"Member",
on_delete=models.PROTECT,
blank=True,
null=True,
related_name="payments",
)
def __str__(self):
"""Return payment id and date."""
return 'Payment no. {}, {}'.format(self.id, str(self.date))
return "Payment no. {}, {}".format(self.id, str(self.date))
@staticmethod
def find_payments_by_name(query_name):
qs = Payment.objects.all()
for term in query_name.split():
qs = qs.filter(Q(member__first_name__icontains=term) | Q(member__last_name__icontains=term))
qs = qs.filter(
Q(member__first_name__icontains=term)
| Q(member__last_name__icontains=term)
)
return qs
class Member(BaseMember):
"""Member model represets one member on the registry."""
created = models.DateTimeField(_('Created'), default=timezone.now)
created = models.DateTimeField(_("Created"), default=timezone.now)
class Meta:
permissions = (
('check_by_email', 'Can check if user exists by email'),
('read_member', 'Can see member in list'),
("check_by_email", "Can check if user exists by email"),
("read_member", "Can see member in list"),
)
verbose_name = _('Member')
verbose_name_plural = _('Members')
verbose_name = _("Member")
verbose_name_plural = _("Members")
@staticmethod
def from_array(array):
@@ -126,5 +134,5 @@ class Member(BaseMember):
@staticmethod
def get_members_with_latest_payment(members_query):
"""Return QuerySet of given members QS with last_paid attribute."""
latest = Payment.objects.filter(member=OuterRef('pk')).order_by('-date')
return members_query.annotate(last_paid=Subquery(latest.values('date')[:1]))
latest = Payment.objects.filter(member=OuterRef("pk")).order_by("-date")
return members_query.annotate(last_paid=Subquery(latest.values("date")[:1]))
+1 -1
View File
@@ -5,4 +5,4 @@ import logging
class CheckByEmailPermission(BasePermission):
def has_permission(self, request, view):
return request.user.has_perm('members.check_by_email')
return request.user.has_perm("members.check_by_email")
+4 -4
View File
@@ -6,7 +6,7 @@ from .models import Member, Payment, Request
class MemberResource(resources.ModelResource):
class Meta:
model = Member
exclude = ['id', 'created']
exclude = ["id", "created"]
class PaymentResource(resources.ModelResource):
@@ -14,13 +14,13 @@ class PaymentResource(resources.ModelResource):
class Meta:
model = Payment
exclude = ['id']
exclude = ["id"]
def dehydrate_member(self, payment):
return '{} {}'.format(payment.member.first_name, payment.member.last_name)
return "{} {}".format(payment.member.first_name, payment.member.last_name)
class ApplicationResource(resources.ModelResource):
class Meta:
model = Request
exclude = ['id']
exclude = ["id"]
+12 -3
View File
@@ -7,11 +7,20 @@ from members.models import Member
class MemberSerializer(serializers.ModelSerializer):
"""Model serializer for member."""
paid = serializers.DateTimeField(source='last_paid')
paid = serializers.DateTimeField(source="last_paid")
class Meta:
"""Meta of member serializer."""
model = Member
fields = ('id', 'first_name', 'last_name', 'email',
'POR', 'AYY', 'jas', 'created', 'paid')
fields = (
"id",
"first_name",
"last_name",
"email",
"POR",
"AYY",
"jas",
"created",
"paid",
)
+33 -14
View File
@@ -1,7 +1,7 @@
"""File containing member application django tables."""
import django_tables2 as tables
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import F, OuterRef, Subquery
from django.utils import timezone
@@ -12,11 +12,16 @@ from members.models import Member, Payment, Request
class MemberTable(tables.Table):
"""Table for member."""
last_paid = tables.DateTimeColumn(verbose_name=_('Last paid'))
last_paid = tables.DateTimeColumn(verbose_name=_("Last paid"))
options = tables.TemplateColumn(
('<a class="data-table-button btn btn-primary" '
'href="/members/edit/{{ record.id }}">') + _('Edit') + '</a>', verbose_name=""
(
'<a class="data-table-button btn btn-primary" '
'href="/members/edit/{{ record.id }}">'
)
+ _("Edit")
+ "</a>",
verbose_name="",
)
class Meta:
@@ -26,24 +31,34 @@ class MemberTable(tables.Table):
def render_last_paid(self, record):
try:
return timezone.localtime(record.payments.filter(member=record).latest('date').date).strftime('%-d.%-m.%Y %H:%M')
return timezone.localtime(
record.payments.filter(member=record).latest("date").date
).strftime("%-d.%-m.%Y %H:%M")
except ObjectDoesNotExist:
return timezone.localtime(record.created).strftime('%-d.%-m.%Y %H:%M') + _(" (not paid)")
return timezone.localtime(record.created).strftime("%-d.%-m.%Y %H:%M") + _(
" (not paid)"
)
def order_last_paid(self, queryset, is_descending):
queryset = Member.get_members_with_latest_payment(queryset).order_by(('-' if is_descending else '') + 'last_paid')
queryset = Member.get_members_with_latest_payment(queryset).order_by(
("-" if is_descending else "") + "last_paid"
)
return (queryset, True)
class PaymentTable(tables.Table):
"""Table for payments."""
member = tables.Column(accessor='member', verbose_name=_('Member'))
member = tables.Column(accessor="member", verbose_name=_("Member"))
options = tables.TemplateColumn(
('<a class="data-table-button btn btn-primary" '
'href="/members/edit_payment/{{ record.id }}">') + _('Edit') + '</a>',
verbose_name=""
(
'<a class="data-table-button btn btn-primary" '
'href="/members/edit_payment/{{ record.id }}">'
)
+ _("Edit")
+ "</a>",
verbose_name="",
)
class Meta:
@@ -56,9 +71,13 @@ class RequestTable(tables.Table):
"""Table for member applications."""
options = tables.TemplateColumn(
('<a class="data-table-button btn btn-primary" '
'href="/members/edit_application/{{ record.id }}">') + _('Edit') + '</a>',
verbose_name=""
(
'<a class="data-table-button btn btn-primary" '
'href="/members/edit_application/{{ record.id }}">'
)
+ _("Edit")
+ "</a>",
verbose_name="",
)
class Meta:
@@ -1,11 +0,0 @@
{% load i18n %}
{% trans "Moi" %} {{ first_name }}!
{% trans "Onnittelut! Sinut on hyväksytty Sähköinsinöörikillan jäseneksi." %}
{% trans "Käy kurkkaamassa killan nettisivuilta" %} (https://sik.ayy.fi) {% trans "tulevia tapahtumia ja piipahda kiltahuoneella tutustumassa uusiin kiltatovereihisi!" %}
{% trans "Liity myös killan TG-kanaville" %}:
{% trans "SIK" %}: https://t.me/joinchat/A6EViD5FCWLxPcXCggY7hw
{% trans "SIK-fuksit 2019" %}: http://tinyurl.com/sikfuksit19-tg
{% trans "SIK-fuksit 2019 -tiedotuskanava" %}: http://tinyurl.com/sikfuksit19-tiedotus
+84 -47
View File
@@ -5,6 +5,7 @@ from unittest import skip
from django.contrib.auth.models import User
from members.models import Member, Payment, Request
from rest_framework.authtoken.models import Token
from datetime import timezone
import logging
@@ -17,16 +18,23 @@ class MemberRegisterTestCase(TestCase):
def setUp(self):
"""Setup testing environment by creating member and admin."""
memb = Member.objects.create(first_name="Tidus", last_name="Tester", email="tidus@tester.fi")
payment = Payment.objects.create(member=memb, source='AYY')
memb = Member.objects.create(
first_name="Tidus", last_name="Tester", email="tidus@tester.fi"
)
payment = Payment.objects.create(member=memb, source="AYY")
appl = Request.objects.create(
first_name="Liisa", last_name="Mattila",
email="liisa.mattila@pylly.com", POR="Kouvola",
AYY=True, jas=False)
first_name="Liisa",
last_name="Mattila",
email="liisa.mattila@pylly.com",
POR="Kouvola",
AYY=True,
jas=False,
)
username, password = 'test_admin', 'password123'
username, password = "test_admin", "password123"
test_admin = User.objects.create_superuser(
username, 'myemail@test.com', password)
username, "myemail@test.com", password
)
self.c = Client()
self.c.login(username=username, password=password)
@@ -39,84 +47,113 @@ class MemberRegisterTestCase(TestCase):
"""Test csv import only with single line in csv file."""
current_dir = os.path.dirname(os.path.abspath(__file__))
with open(os.path.join(current_dir, 'test_resources', 'single_line_import.csv')) as csvFile:
response = self.c.post('/members/import_csv', {
'csvFile': csvFile,
'delimiter': ';',
'payment_source': 'AYY'
}, follow=True)
with open(
os.path.join(current_dir, "test_resources", "single_line_import.csv")
) as csvFile:
response = self.c.post(
"/members/import_csv",
{"csvFile": csvFile, "delimiter": ";", "payment_source": "AYY"},
follow=True,
)
self.assertEqual(response.status_code, 200)
def test_import_csv_multi_line(self):
"""Test csv import with multilined csv."""
current_dir = os.path.dirname(os.path.abspath(__file__))
with open(os.path.join(current_dir, 'test_resources', 'multi_line_import.csv')) as csvFile:
response = self.c.post('/members/import_csv', {
'csvFile': csvFile,
'delimiter': ';',
'payment_source': 'AYY'
}, follow=True)
with open(
os.path.join(current_dir, "test_resources", "multi_line_import.csv")
) as csvFile:
response = self.c.post(
"/members/import_csv",
{"csvFile": csvFile, "delimiter": ";", "payment_source": "AYY"},
follow=True,
)
self.assertEqual(response.status_code, 200)
def test_autocomplete_search_found(self):
"""Test member autocomplete search"""
search_terms = 'Tidus'
response = self.c.get('/members/member-autocomplete?q={}'.format(search_terms), follow=True)
results = response.json()['results']
search_terms = "Tidus"
response = self.c.get(
"/members/member-autocomplete?q={}".format(search_terms), follow=True
)
results = response.json()["results"]
self.assertEqual(len(results), 1)
def test_autocomplete_search_not_found(self):
"""Test member autocomplete search"""
search_terms = 'Notfound'
response = self.c.get('/members/member-autocomplete?q={}'.format(search_terms), follow=True)
results = response.json()['results']
search_terms = "Notfound"
response = self.c.get(
"/members/member-autocomplete?q={}".format(search_terms), follow=True
)
results = response.json()["results"]
self.assertEqual(len(results), 0)
def test_export_members_excel(self):
"""Test if the user can download an excel file of the member register"""
resp = self.c.get('/members/export_members')
content_type = 'application/vnd.ms-excel'
self.assertIn(content_type, resp['Content-Type'])
resp = self.c.get("/members/export_members")
content_type = "application/vnd.ms-excel"
self.assertIn(content_type, resp["Content-Type"])
content = resp.content
arrays = pyexcel.get_array(file_content=content, file_type='xlsx')
tidus_array = ['Tidus', 'Tester', 'tidus@tester.fi', '', '0', '0']
arrays = pyexcel.get_array(file_content=content, file_type="xlsx")
tidus_array = ["Tidus", "Tester", "tidus@tester.fi", "", "0", "0"]
self.assertIn(tidus_array, arrays)
def test_export_payments_excel(self):
"""Test if the user can download an excel file of the payment register"""
resp = self.c.get('/members/export_payments')
content_type = 'application/vnd.ms-excel'
self.assertIn(content_type, resp['Content-Type'])
resp = self.c.get("/members/export_payments")
content_type = "application/vnd.ms-excel"
self.assertIn(content_type, resp["Content-Type"])
content = resp.content
arrays = pyexcel.get_array(file_content=content, file_type='xlsx')
created = Payment.objects.get(member__email='tidus@tester.fi').date.strftime('%Y-%m-%d %H:%M:%S')
tidus_array = ['Tidus Tester', created, 'AYY']
arrays = pyexcel.get_array(file_content=content, file_type="xlsx")
created = (
Payment.objects.get(member__email="tidus@tester.fi")
.date.replace(tzinfo=timezone.utc)
.astimezone(tz=None)
.strftime("%Y-%m-%d %H:%M:%S")
)
tidus_array = ["Tidus Tester", created, "AYY"]
self.assertIn(tidus_array, arrays)
def test_export_applications_excel(self):
"""Test if the user can download an excel file of the member application register"""
resp = self.c.get('/members/export_applications')
content_type = 'application/vnd.ms-excel'
self.assertIn(content_type, resp['Content-Type'])
resp = self.c.get("/members/export_applications")
content_type = "application/vnd.ms-excel"
self.assertIn(content_type, resp["Content-Type"])
content = resp.content
arrays = pyexcel.get_array(file_content=content, file_type='xlsx')
submitted = Request.objects.get(email='liisa.mattila@pylly.com').submitted.strftime('%Y-%m-%d %H:%M:%S')
liisa_array = ['Liisa', 'Mattila', 'liisa.mattila@pylly.com', 'Kouvola', '1', '0', submitted]
arrays = pyexcel.get_array(file_content=content, file_type="xlsx")
submitted = (
Request.objects.get(email="liisa.mattila@pylly.com")
.submitted.replace(tzinfo=timezone.utc)
.astimezone(tz=None)
.strftime("%Y-%m-%d %H:%M:%S")
)
liisa_array = [
"Liisa",
"Mattila",
"liisa.mattila@pylly.com",
"Kouvola",
"1",
"0",
submitted,
]
self.assertIn(liisa_array, arrays)
def test_submit_member_application(self):
"""Test if submitting a member application works"""
data = {
'first_name': 'Seppo', 'last_name': 'Saastamoinen',
'email': 'seppo@saastamoin.en', 'jas': 'on',
'POR': 'Dipolin viinibaari'
"first_name": "Seppo",
"last_name": "Saastamoinen",
"email": "seppo@saastamoin.en",
"jas": "on",
"POR": "Dipolin viinibaari",
}
resp = self.c.post('/members/submit_application', data=data)
resp = self.c.post("/members/submit_application", data=data)
self.assertEqual(resp.status_code, 200)
self.assertTrue(Request.objects.filter(email='seppo@saastamoin.en').exists())
self.assertTrue(Request.objects.filter(email="seppo@saastamoin.en").exists())
+2 -2
View File
@@ -6,10 +6,10 @@ from rest_framework.throttling import UserRateThrottle
class BurstRateThrottle(UserRateThrottle):
"""Class for burst rate throttle."""
scope = 'burst'
scope = "burst"
class SustainedRateThrottle(UserRateThrottle):
"""Class for sustained rate throttle."""
scope = 'sustained'
scope = "sustained"
+36 -57
View File
@@ -1,6 +1,6 @@
"""File containing Member application URLs."""
from django.conf.urls import url
from django.urls import re_path
from django.conf import settings
from django.contrib.auth.decorators import login_required, permission_required
@@ -41,86 +41,65 @@ from members.views import application_submit
# from members.views import validateEmail, validate_success, validate_fail
urlpatterns = [
# landing page
url(r'^$', member_list),
url(r'^list$', member_list),
re_path(r"^$", member_list),
re_path(r"^list$", member_list),
# add member form view
url(r'^add$', member_add),
re_path(r"^add$", member_add),
# add many members view
url(r'^add_many$', member_add_many),
re_path(r"^add_many$", member_add_many),
# edit member information view
url(r'^edit/(?P<index>\d+)$', member_edit),
re_path(r"^edit/(?P<index>\d+)$", member_edit),
# delete confirmation view
url(r'^delete_member_confirm/(?P<index>\d+)$', member_delete_confirm),
re_path(r"^delete_member_confirm/(?P<index>\d+)$", member_delete_confirm),
# list all member applications
url(r'^applications$', application_list),
re_path(r"^applications$", application_list),
# edit member application
url(r'^edit_application/(?P<index>\d+)$', application_edit),
re_path(r"^edit_application/(?P<index>\d+)$", application_edit),
# post request targets
url(r'^submit_member$', member_submit),
url(r'^update_member$', member_update),
url(r'^delete_member$', member_delete),
url(r'^submit_payment$', payment_submit),
url(r'^update_payment$', payment_update),
url(r'^delete_payment$', payment_delete),
url(r'^submit_application$', application_submit),
url(r'^accept_application$', application_accept),
url(r'^delete_application$', application_delete),
re_path(r"^submit_member$", member_submit),
re_path(r"^update_member$", member_update),
re_path(r"^delete_member$", member_delete),
re_path(r"^submit_payment$", payment_submit),
re_path(r"^update_payment$", payment_update),
re_path(r"^delete_payment$", payment_delete),
re_path(r"^submit_application$", application_submit),
re_path(r"^accept_application$", application_accept),
re_path(r"^delete_application$", application_delete),
# the actual member application form
url(r'^application/$', application_form),
re_path(r"^application/$", application_form),
# delete confirmation view for applications
url(r'^delete_application_confirm/(?P<index>\d+)$',
application_delete_confirm),
re_path(r"^delete_application_confirm/(?P<index>\d+)$", application_delete_confirm),
# list all payment events
url(r'^payments$', payment_list),
re_path(r"^payments$", payment_list),
# add payment event
url(r'^add_payment$', payment_add),
re_path(r"^add_payment$", payment_add),
# edit payment event
url(r'^edit_payment/(?P<index>\d+)$', payment_edit),
re_path(r"^edit_payment/(?P<index>\d+)$", payment_edit),
# delete confirmation view
url(r'^delete_payment_confirm/(?P<index>\d+)$', payment_delete_confirm),
re_path(r"^delete_payment_confirm/(?P<index>\d+)$", payment_delete_confirm),
# post endpoint for confirming multiple entries
url(r'^add_many_confirm$', add_many_confirm),
re_path(r"^add_many_confirm$", add_many_confirm),
# settings page
url(r'^settings$', settings_page),
re_path(r"^settings$", settings_page),
# send CSV member data by POST
url(r'^import_csv', import_csv),
re_path(r"^import_csv", import_csv),
# export members as excel file
url(r'export_members', export_members_excel),
url(r'export_payments', export_payments_excel),
url(r'export_applications', export_applications_excel),
re_path(r"export_members", export_members_excel),
re_path(r"export_payments", export_payments_excel),
re_path(r"export_applications", export_applications_excel),
# rest api url
url(r'^api/members/(?P<pk>\d+)$', MemberDetail.as_view()),
re_path(r"^api/members/(?P<pk>\d+)$", MemberDetail.as_view()),
# member select autocomplete view
url(
r'^member-autocomplete/$',
re_path(
r"^member-autocomplete/$",
MemberAutoComplete.as_view(),
name='member-autocomplete',
name="member-autocomplete",
),
url(r'^check', CheckByEmail.as_view())
re_path(r"^check", CheckByEmail.as_view()),
]
if settings.DEBUG:
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
urlpatterns += staticfiles_urlpatterns()
+1 -1
View File
@@ -1,4 +1,4 @@
"""File containing Members application views."""
from django.conf import settings
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
+79 -62
View File
@@ -4,7 +4,7 @@ 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.conf import settings
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from django.forms.models import model_to_dict
from django.template.loader import render_to_string
@@ -12,6 +12,7 @@ import logging
import html
from webapp.utils import send_email
from webapp.utils import add_to_mailinglist
from members.views.utils import *
from members.tables import RequestTable
@@ -21,52 +22,55 @@ from members.views import error_view
@ensure_csrf_cookie
@require_http_methods(["GET"])
@login_required(login_url='/admin/login')
@permission_required('members.read_application', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("members.read_application", raise_exception=True)
def application_list(request, *args, **kwargs):
"""List member applications not yet processed."""
applications = Request.objects.all()
application_count = len(applications)
table = RequestTable(applications,
request=request,
exclude=['id'],
attrs={'class': 'table table-bordered table-hover'})
table = RequestTable(
applications,
request=request,
exclude=["id"],
attrs={"class": "table table-bordered table-hover"},
)
table.paginate(page=request.GET.get('page', 1), per_page=25)
table.paginate(page=request.GET.get("page", 1), per_page=25)
table_html = convert_table_to_html(table, request)
context = {
'table': table_html,
'application_count': application_count,
'notification': request.GET.get('notification', None)
"table": table_html,
"application_count": application_count,
"notification": request.GET.get("notification", None),
}
return render(request, 'application_list.html', context)
return render(request, "members/application_list.html", context)
@ensure_csrf_cookie
@require_http_methods(["GET"])
@login_required(login_url='/admin/login')
@permission_required('members.change_request', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("members.change_request", raise_exception=True)
def application_edit(request, *args, **kwargs):
"""Edit member request information."""
i = kwargs.pop('index', None)
i = kwargs.pop("index", None)
if i is None:
return error_view(request, _('No application id specified'))
return error_view(request, _("No application id specified"))
else:
application = Request.objects.get(id=i)
form = ApplicationForm(instance=application)
return render(
request,
'application_edit.html',
{'application_id': i, 'form': form})
"members/application_edit.html",
{"application_id": i, "form": form},
)
@ensure_csrf_cookie
@require_http_methods(["POST"])
@login_required(login_url='/admin/login')
@permission_required('members.add_member', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("members.add_member", raise_exception=True)
def application_accept(request, *args, **kwargs):
"""Accept application."""
id = request.POST.get('id', None)
id = request.POST.get("id", None)
if id is not None:
application = Request.objects.get(id=id)
else:
@@ -78,9 +82,15 @@ def application_accept(request, *args, **kwargs):
application = form.save()
if Member.objects.filter(email=application.email).exists():
return error_view(request, _(
'Email {} is already in use by a member. Application cannot be accepted.'
).format(application.email))
return error_view(
request,
_(
"Email {} is already in use by a member. Application cannot be accepted."
).format(application.email),
)
if application.jas:
add_to_mailinglist(application.email)
member = application.to_member()
member.save()
@@ -88,24 +98,25 @@ def application_accept(request, *args, **kwargs):
logging.info(
"Accepted application in member "
"register with the following info: {}"
.format(form))
notification = "{} {}.".format(_("Successfully accepted application"),
str(application))
"register with the following info: {}".format(form)
)
notification = "{} {}.".format(
_("Successfully accepted application"), str(application)
)
subject = _('Jäsenhakemuksesi Sähköinsinöörikiltaan on hyväksytty!')
subject = _("Jäsenhakemuksesi Sähköinsinöörikiltaan on hyväksytty!")
message = render_to_string(
'members:email_application_accept.html', {
'first_name': application.first_name
}
"members/email_application_accept.html",
{"first_name": application.first_name},
)
send_email(member.email, subject, message)
send_email(member.email, subject, message, True)
return HttpResponseRedirect(
'/members/list?notification={}'.format(html.escape(notification)))
"/members/list?notification={}".format(html.escape(notification))
)
except Exception as ex:
logging.exception('Exception while accepting application')
logging.exception("Exception while accepting application")
return error_view(request, str(ex))
else:
logging.info(form)
@@ -114,52 +125,55 @@ def application_accept(request, *args, **kwargs):
@ensure_csrf_cookie
@require_http_methods(["POST"])
@login_required(login_url='/admin/login')
@permission_required('members.delete_request', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("members.delete_request", raise_exception=True)
def application_delete(request, *args, **kwargs):
"""Delete member application."""
try:
id = request.POST['id']
id = request.POST["id"]
except KeyError:
return error_view(request, _('No application id specified'))
return error_view(request, _("No application id specified"))
try:
application = Request.objects.get(id=id)
notification = "{} {}.".format(_("Successfully deleted application"),
str(application))
notification = "{} {}.".format(
_("Successfully deleted application"), str(application)
)
application.delete()
logging.info(
"Delete application in member register with the following id: {}"
.format(id))
"Delete application in member register with the following id: {}".format(id)
)
return HttpResponseRedirect(
'/members/applications?notification={}'
.format(html.escape(notification)))
"/members/applications?notification={}".format(html.escape(notification))
)
except:
return error_view(request, _('Could not delete application object'))
return error_view(request, _("Could not delete application object"))
@ensure_csrf_cookie
@require_http_methods(["GET"])
@login_required(login_url='/admin/login')
@permission_required('members.delete_request', raise_exception=True)
@login_required(login_url="/admin/login")
@permission_required("members.delete_request", raise_exception=True)
def application_delete_confirm(request, *args, **kwargs):
"""Confirm application deletion."""
i = kwargs.pop('index', None)
i = kwargs.pop("index", None)
if i is None:
return error_view(request, _('No application id specified'))
return error_view(request, _("No application id specified"))
else:
application = Request.objects.get(id=i)
form = ApplicationForm(instance=application)
return render(request,
'application_delete_confirm.html',
{'application_id': i, 'form': form})
return render(
request,
"members/application_delete_confirm.html",
{"application_id": i, "form": form},
)
@ensure_csrf_cookie
def application_form(request, *args, **kwargs):
"""Render member application form."""
form = ApplicationForm()
return render(request, 'application_index.html', {'form': form})
return render(request, "members/application_index.html", {"form": form})
@ensure_csrf_cookie
@@ -171,19 +185,22 @@ def application_submit(request, *args, **kwargs):
form.save()
try:
application = form.instance
email = form.cleaned_data.get('email', '')
email = form.cleaned_data.get("email", "")
subject = _('Jäsenhakemuksesi Sähköinsinöörikiltaan on lähetetty onnistuneesti!')
subject = _(
"Jäsenhakemuksesi Sähköinsinöörikiltaan on lähetetty onnistuneesti!"
)
message = render_to_string(
'members:email_application_submit.html', {
'application': application,
'ayy': _('Kyllä') if application.AYY else _('Ei'),
'jas': _('Kyllä') if application.jas else _('Ei')
}
"members/email_application_submit.html",
{
"application": application,
"ayy": _("Kyllä") if application.AYY else _("Ei"),
"jas": _("Kyllä") if application.jas else _("Ei"),
},
)
send_email(email, subject, message)
finally:
return render(request, 'application_success.html', {})
return render(request, "members/application_success.html", {})
else:
return error_view(request, form.errors)

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