127 Commits

Author SHA1 Message Date
jadera 8393875963 change olhev date 2026-01-15 15:03:37 +02:00
SimeonPursiainen d5f67f4cc1 Edit GDPR link in Kaehmy site 2025-11-21 22:48:49 +02:00
J4DER4 ebd0bd9fa2 Merge branch 'Testing-audit-fixes-pt1' into 'main'
PIP upgrades for audit safetyscheck

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!117
2025-11-20 17:13:26 +00:00
J4DER4 c0a9321341 Merge branch 'jäsen_searchbar' into 'main'
added searchbar to jäsenrekisteri (check description)

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!116
2025-11-20 17:02:59 +00:00
jadera 6693279348 use strict version 2025-11-20 18:45:13 +02:00
jadera d0557ffb79 added more pip upgrades 2025-11-20 00:59:30 +02:00
jadera 91ee3bea6d testing pip upgrade to bump packages 2025-11-20 00:43:47 +02:00
jadera b1d6bf359f added searchbar to jäsenrekisteri
added searchbar
2025-11-19 23:37:48 +02:00
Justus Ojala 61ac177ce3 Made manual submit_id check and IntegrityError messages the same (were different to distinguish which is blocking the request when running locally) 2025-10-13 20:37:09 +03:00
Justus Ojala 9a2168e47f Add manual submit_id uniqueness check as it is not enforced on server 2025-10-13 20:20:29 +03:00
Justus Ojala a5732669da Merge branch 'signup-duplicate-reduction' into 'main'
Add submit_id to signup model

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!115
2025-10-13 19:45:46 +03:00
Justus Ojala 75800ee9ee Add submit_id to signup model 2025-10-13 19:33:26 +03:00
Justus Ojala 5782c20b4b Fix more name errors 2025-09-23 08:08:39 +03:00
Justus Ojala 167b0bfabf Fix more name errors 2025-09-23 07:52:01 +03:00
Justus Ojala 43c9a6d328 Merge branch 'print-syntax' into 'main'
This isn't C

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!113
2025-09-22 18:50:27 +03:00
Justus Ojala b3a159b3d8 This isn't C 2025-09-22 18:46:57 +03:00
Justus Ojala a7ed188dc8 Merge branch 'debugprints' into 'main'
added debugprints

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!112
2025-09-22 18:25:55 +03:00
Justus Ojala 162759dcb2 added debugprints 2025-09-22 18:22:08 +03:00
Justus Ojala 05e6ba01d9 Merge branch 'time-name-fix' into 'main'
Import time

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!111
2025-09-22 18:04:59 +03:00
Justus Ojala 8a061381f4 Import time 2025-09-22 18:01:27 +03:00
Justus Ojala d2fa7084da Merge branch 'setuptools' into 'main'
Setuptools

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!110
2025-09-22 17:45:46 +03:00
Justus Ojala e7278c8893 Remove poetry.lock from version control 2025-09-22 17:38:47 +03:00
Justus Ojala 52bf21c8ba Add setuptools dependency 2025-09-22 17:38:00 +03:00
Justus Ojala 0cbc794c75 Merge branch 'signup_duplicate_prevention' into 'main'
Added submission key checking to backend

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!106
2025-09-16 21:43:28 +03:00
Justus Ojala 90a0550775 Added submission key checking to backend 2025-09-16 21:24:52 +03:00
Aarni Halinen 0d458cf2ea Merge branch 'deps' into 'main'
Update some deps

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!105
2025-03-26 21:37:14 +02:00
Aarni Halinen 5155f52f29 update djangorestframework-simplejwt 2025-03-26 21:33:23 +02:00
Aarni Halinen a2ccc43a36 update gunicorn 2025-03-26 21:16:22 +02:00
Aarni Halinen a045c6ac89 update sentry-sdk 2025-03-26 21:13:17 +02:00
Aarni Halinen dcb2115cb5 poetry update 2025-03-26 21:12:51 +02:00
Aarni Halinen 5c7528ca6a Merge branch 'dev-setup' into 'main'
Update poetry and fix development tooling and docs

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!104
2025-03-26 21:07:01 +02:00
Aarni Halinen a3ab12619a EOF new line 2025-03-26 21:03:52 +02:00
Aarni Halinen c637ffb3f6 stop using env file in docker-compose 2025-03-26 20:58:05 +02:00
Aarni Halinen 41fd3043d0 update poetry to v2.1.1 2025-03-26 20:51:02 +02:00
Aarni Halinen 1331eeb1d7 change trunk and production branches 2025-03-06 02:36:14 +02:00
Aarni Halinen 1fd329f0c1 pick changes from templates 2025-03-06 02:31:56 +02:00
Aarni Halinen 52eb9e370c Revert "production deployments manually from develop branch"
This reverts commit 00e5eff8db.
2025-03-06 02:31:02 +02:00
Aarni Halinen 00e5eff8db production deployments manually from develop branch 2025-03-06 02:10:19 +02:00
SimeonPursiainen 7554c1e7e8 testing 2025-02-28 22:46:40 +02:00
Simeon Pursiainen 50fd4ff9f7 Merge branch 'update-python' into 'develop'
Update python to 3.12

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!99
2025-02-09 19:53:50 +00:00
Aarni Halinen ff8230d9c0 update node packages 2025-02-09 21:20:38 +02:00
Aarni Halinen 3f73fbec62 update node to v22 2025-02-09 21:08:18 +02:00
Aarni Halinen 916e0bdaf0 allow audit failure 2025-02-09 21:05:20 +02:00
Aarni Halinen 9e4a7c8569 poetry update 2025-02-09 20:56:21 +02:00
Aarni Halinen b5839da135 rm deprecated compose version 2025-02-09 20:54:05 +02:00
Aarni Halinen f562912492 migrate to new dependecy format 2025-02-09 20:54:04 +02:00
Aarni Halinen f0a5b6e8e7 update to python3.12 2025-02-09 19:47:10 +02:00
Aarni Halinen 8fc3ee534d fix markdown lint issues in README 2025-02-09 18:27:18 +02:00
Aarni Halinen 9b4fa56add uppercase AS in Dockerfile 2025-02-09 18:22:15 +02:00
Simeon Pursiainen 90ca91970d Update .gitlab-ci.yml file 2025-01-23 19:11:25 +00:00
Simeon Pursiainen 955072370f temporary fix 2025-01-23 19:05:17 +00:00
Simeon Pursiainen 709275c4d3 Edit email.html 2025-01-23 18:52:54 +00:00
Simeon Pursiainen 6f61c9dc32 Do audit only on push to master 2025-01-23 18:51:46 +00:00
Simeon Pursiainen f5432a1ff9 Edited Hafv date 2025-01-21 21:11:01 +00:00
Johannes Viirimäki f23ce6b39e use docker:25 based images and services 2024-09-30 07:36:45 +00:00
Johannes Viirimäki 3a8c455031 Update kaehmy.html 2024-09-30 07:25:41 +00:00
Johannes a6a973f008 update pip & pillow 2024-01-24 23:22:39 +02:00
Johannes 01f7911352 update packages 2024-01-24 22:02:05 +02:00
johannes 55507f89d1 change hafv date 2024-01-24 20:49:34 +02: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 8550a9a02b prkl 2023-10-04 20:46:06 +03:00
Tommi S 703bb91bfd Edit signup email contact 2023-10-04 20:04:20 +03: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 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 fc6e02b71b Add log level to gunicorn 2023-03-27 19:16:08 +03:00
Ojakoo 8815ccf667 update django 2023-03-05 19:53:44 +02:00
Ojakoo b694370572 :D 2023-03-05 19:35:51 +02: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 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
Ojakoo 5e1390ab6b Changes to kaehmy categories 2022-10-05 14:18:28 +03:00
Ojakoo 99348dc297 Update dparse 2022-10-05 12:58:29 +03:00
Ojakoo 715b309c89 Added new categories for kaehmy 2022-10-05 12:43:52 +03:00
Ojakoo 70676d5203 Lint 2022-09-23 20:53:25 +03:00
Ojakoo 037e4ae6e8 Use html templates for kaehmy emails. No translations for now. 2022-09-23 20:30:08 +03:00
Ojakoo 8d6f13b61d Update privacy policy link 2022-09-23 17:57:36 +03:00
Ojakoo 03982ee620 Update kaehmy dates and change header to html instead of picture. 2022-09-23 17:51:01 +03:00
Ojakoo a8923b63d6 wrap private key 2022-09-13 13:31:58 +03:00
Ojakoo 19975877cb Remove debug stuff 2022-09-13 09:59:55 +03:00
Ojakoo 2e0fad4bb2 Use POSIX format for source 2022-09-12 22:42:01 +03:00
Ojakoo f0179c1840 Change google creds format. Ugly but works. 2022-09-12 22:38:18 +03:00
Ojakoo 37a9750d4d Add group key and dev secrets to stack compose 2022-09-05 13:38:26 +03:00
Ojakoo 5575186570 lint 2022-09-05 12:03:31 +03:00
Ojakoo 9e179d5e06 Added error handling for incorrectly formatted google creds 2022-09-05 10:40:29 +03:00
Ojakoo ea9a732803 Modified paths 2022-08-17 23:00:11 +03:00
Ojakoo 0026b788b2 juuh eli 2022-08-16 23:00:36 +03:00
Ojakoo da3a484f6c wbn? 2022-08-16 22:24:20 +03:00
Ojakoo a310d51f5e or not 2022-08-16 21:43:04 +03:00
Ojakoo 6732e30213 #14 fix paths 2022-08-16 21:26:55 +03:00
Ojakoo 4e59eee200 Enable GOOGLE_SERVICE_ACCOUNT, should work now.. 2022-08-16 20:59:19 +03:00
Aarni Halinen bb0b2a2628 Fix kaehmy form 2022-08-11 11:28:30 +03:00
Aarni Halinen 32d636d3ee Disable GOOGLE_SERVICE_ACCOUNT for debugging 2022-08-11 09:52:58 +03:00
Ojakoo c91b99cdb1 lol 2022-08-09 21:44:01 +03:00
Aarni Halinen 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
103 changed files with 7430 additions and 7813 deletions
-4
View File
@@ -1,9 +1,5 @@
[report] [report]
show_missing = True show_missing = True
omit =
*/migrations/*
*/admin.py
*/translation.py
[run] [run]
omit = omit =
*/migrations/* */migrations/*
+4 -2
View File
@@ -1,11 +1,13 @@
DEPLOY_ENV=local DEPLOY_ENV=local
SENTRY_DSN= SENTRY_DSN=
HOST=api.dev.sahkoinsinoorikilta.fi HOST=localhost
DEBUG=True DEBUG=True
SECRET_KEY=7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp( SECRET_KEY=7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp(
DB_NAME=postgres DB_NAME=postgres
DB_USER=postgres DB_USER=postgres
DB_PASSWD=postgres DB_PASSWD=postgres
DB_HOST=db DB_HOST=localhost
DB_PORT=5432 DB_PORT=5432
EMAIL_API_KEY= EMAIL_API_KEY=
GROUP_KEY=
GOOGLE_CREDS='{}'
+1 -1
View File
@@ -10,4 +10,4 @@ DB_HOST=db
DB_PORT=5432 DB_PORT=5432
EMAIL_API_KEY= EMAIL_API_KEY=
GROUP_KEY= GROUP_KEY=
GOOGLE_CREDS_JSON='{}' GOOGLE_CREDS='{}'
-6
View File
@@ -1,6 +0,0 @@
members/static/js/lib
infoscreen/static/js/lib
webapp/static/js/lib
static/js/lib
collected_static
venv
-251
View File
@@ -1,251 +0,0 @@
{
"env": {
"browser": true,
"jquery": true
},
"globals": {
"angular": true,
"noty": true,
"_": true,
"moment": true
},
"extends": "eslint:recommended",
"rules": {
"no-unused-vars": "warn",
"accessor-pairs": "error",
"array-bracket-spacing": "off",
"array-callback-return": "error",
"arrow-body-style": "error",
"arrow-parens": "error",
"arrow-spacing": "error",
"block-scoped-var": "off",
"block-spacing": "off",
"brace-style": "off",
"callback-return": "off",
"camelcase": "off",
"capitalized-comments": "off",
"class-methods-use-this": "error",
"comma-dangle": "off",
"comma-spacing": "off",
"comma-style": "off",
"complexity": "off",
"computed-property-spacing": "off",
"consistent-return": "off",
"consistent-this": "off",
"curly": "off",
"default-case": "off",
"dot-location": [
"error",
"property"
],
"dot-notation": "off",
"eol-last": "off",
"eqeqeq": "off",
"func-call-spacing": "error",
"func-name-matching": "error",
"func-names": "off",
"func-style": "off",
"generator-star-spacing": "error",
"global-require": "off",
"guard-for-in": "off",
"handle-callback-err": "off",
"id-blacklist": "error",
"id-length": "off",
"id-match": "error",
"indent": "off",
"init-declarations": "off",
"jsx-quotes": "error",
"key-spacing": "off",
"keyword-spacing": "off",
"line-comment-position": "off",
"linebreak-style": "off",
"lines-around-comment": "off",
"lines-around-directive": "off",
"max-depth": "off",
"max-len": "off",
"max-lines": "off",
"max-nested-callbacks": "error",
"max-params": "off",
"max-statements": "off",
"max-statements-per-line": "off",
"multiline-ternary": "off",
"new-parens": "off",
"newline-after-var": "off",
"newline-before-return": "off",
"newline-per-chained-call": "off",
"no-alert": "error",
"no-array-constructor": "off",
"no-await-in-loop": "error",
"no-bitwise": "off",
"no-caller": "error",
"no-catch-shadow": "off",
"no-confusing-arrow": "error",
"no-constant-condition": [
"error",
{
"checkLoops": false
}
],
"no-continue": "off",
"no-div-regex": "error",
"no-duplicate-imports": "error",
"no-else-return": "off",
"no-empty-function": "off",
"no-eq-null": "off",
"no-eval": "error",
"no-extend-native": "error",
"no-extra-bind": "error",
"no-extra-label": "error",
"no-extra-parens": "off",
"no-floating-decimal": "off",
"no-implicit-coercion": [
"error",
{
"boolean": false,
"number": false,
"string": false
}
],
"no-implicit-globals": "off",
"no-implied-eval": "error",
"no-inline-comments": "off",
"no-inner-declarations": [
"error",
"functions"
],
"no-invalid-this": "off",
"no-iterator": "error",
"no-label-var": "error",
"no-labels": "error",
"no-lone-blocks": "error",
"no-lonely-if": "off",
"no-loop-func": "error",
"no-magic-numbers": "off",
"no-mixed-operators": "off",
"no-mixed-requires": "error",
"no-multi-assign": "off",
"no-multi-spaces": "off",
"no-multi-str": "error",
"no-multiple-empty-lines": "off",
"no-native-reassign": "off",
"no-negated-condition": "off",
"no-negated-in-lhs": "error",
"no-nested-ternary": "off",
"no-new": "error",
"no-new-func": "off",
"no-new-object": "error",
"no-new-require": "error",
"no-new-wrappers": "error",
"no-octal-escape": "error",
"no-param-reassign": "off",
"no-path-concat": "error",
"no-plusplus": "off",
"no-process-env": "error",
"no-process-exit": "error",
"no-proto": "error",
"no-prototype-builtins": "off",
"no-restricted-globals": "error",
"no-restricted-imports": "error",
"no-restricted-modules": "error",
"no-restricted-properties": "error",
"no-restricted-syntax": "error",
"no-return-assign": "off",
"no-return-await": "error",
"no-script-url": "error",
"no-self-compare": "error",
"no-sequences": "off",
"no-shadow": "off",
"no-shadow-restricted-names": "error",
"no-spaced-func": "error",
"no-sync": "error",
"no-tabs": "off",
"no-template-curly-in-string": "error",
"no-ternary": "off",
"no-throw-literal": "off",
"no-trailing-spaces": "off",
"no-undef-init": "error",
"no-undefined": "off",
"no-underscore-dangle": "off",
"no-unmodified-loop-condition": "error",
"no-unneeded-ternary": [
"error",
{
"defaultAssignment": true
}
],
"no-unused-expressions": "off",
"no-use-before-define": "off",
"no-useless-call": "off",
"no-useless-computed-key": "error",
"no-useless-concat": "error",
"no-useless-constructor": "error",
"no-useless-escape": "off",
"no-useless-rename": "error",
"no-useless-return": "error",
"no-var": "off",
"no-void": "off",
"no-warning-comments": "off",
"no-whitespace-before-property": "error",
"no-with": "error",
"object-curly-newline": "off",
"object-curly-spacing": "off",
"object-property-newline": "off",
"object-shorthand": "off",
"one-var": "off",
"one-var-declaration-per-line": "off",
"operator-assignment": "off",
"operator-linebreak": "off",
"padded-blocks": "off",
"prefer-arrow-callback": "off",
"prefer-const": "error",
"prefer-destructuring": [
"error",
{
"array": false,
"object": false
}
],
"prefer-numeric-literals": "error",
"prefer-promise-reject-errors": "error",
"prefer-reflect": "off",
"prefer-rest-params": "off",
"prefer-spread": "off",
"prefer-template": "off",
"quote-props": "off",
"quotes": "off",
"radix": "off",
"require-await": "error",
"require-jsdoc": "off",
"rest-spread-spacing": "error",
"semi": "off",
"semi-spacing": "off",
"sort-imports": "error",
"sort-keys": "off",
"sort-vars": "off",
"space-before-blocks": "off",
"space-before-function-paren": "off",
"space-in-parens": "off",
"space-infix-ops": "off",
"space-unary-ops": [
"error",
{
"nonwords": false,
"words": false
}
],
"spaced-comment": "off",
"strict": "off",
"symbol-description": "error",
"template-curly-spacing": "error",
"unicode-bom": [
"error",
"never"
],
"valid-jsdoc": "off",
"vars-on-top": "off",
"wrap-iife": "off",
"wrap-regex": "off",
"yield-star-spacing": "error",
"yoda": "off"
}
}
+2 -1
View File
@@ -11,4 +11,5 @@ node_modules/
.idea/ .idea/
*.code-workspace *.code-workspace
venv/ venv/
.venv/ .venv/
poetry.lock
+172 -112
View File
@@ -1,131 +1,191 @@
stages: stages:
- setup - setup
- audit - audit
- lint - lint
- test - test
- publish - publish
- deploy - deploy
- cleanup
install: install:
image: node:14 image: node:22
stage: setup stage: setup
script: only:
- npm ci - pushes
artifacts: script:
paths: - npm ci
- node_modules artifacts:
expire_in: 1 week paths:
- node_modules
expire_in: 1 week
audit: audit:
image: python:3.9 image: python:3.12.9
stage: audit stage: audit
needs: [] allow_failure: true
before_script: only:
- pip install poetry==1.1.13 - pushes
- poetry config virtualenvs.create false needs: []
- poetry install --no-interaction --no-ansi before_script:
script: - pip install pip==25.3
- safety check - pip install poetry==2.1.1
- poetry config virtualenvs.create false
- poetry install --no-interaction --no-ansi
script:
- safety check
test: test:
image: python:3.9 image: python:3.12.9
stage: test stage: test
needs: [] only:
services: - pushes
- postgres:12 needs: []
variables: services:
POSTGRES_DB: ci - postgres:12
POSTGRES_USER: postgres variables:
POSTGRES_PASSWORD: postgres POSTGRES_DB: ci
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB" POSTGRES_USER: postgres
DB_HOST: postgres POSTGRES_PASSWORD: postgres
before_script: DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
- pip install poetry==1.1.13 DB_HOST: postgres
- poetry config virtualenvs.create false before_script:
- poetry install --no-interaction --no-ansi - pip install pip==25.3
script: - pip install poetry==2.1.1
- python manage.py migrate --noinput - poetry config virtualenvs.create false
- python manage.py createdefaultadmin - poetry install --no-interaction --no-ansi
- python manage.py test script:
- python manage.py migrate --noinput
- python manage.py createdefaultadmin
- python manage.py test
lint:py: lint:py:
image: python:3.9 image: python:3.12.9
stage: lint stage: lint
needs: [] only:
script: - pushes
- pip install black==22.3.0 needs: []
- black --check . script:
- pip install black==22.3.0
- black --check .
lint:js: lint:js:
image: node:14 image: node:22
stage: lint stage: lint
needs: ["install"] only:
script: - pushes
- npm run lint:js needs: ["install"]
script:
- npm run lint:js
lint:md: lint:md:
image: node:14 image: node:22
stage: lint stage: lint
needs: ["install"] only:
script: - pushes
- npm run lint:md needs: ["install"]
script:
- npm run lint:md
publish: publish:
stage: publish image: docker:25-cli
image: docker:stable stage: publish
needs: ["test", "lint:py", "lint:js", "lint:md"] needs: ["test", "lint:py", "lint:js", "lint:md"]
services: services:
- docker:stable-dind - docker:25-dind
only: only:
- develop - main
- master - production
script: script:
- docker info - docker info
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- docker build . -t "$IMAGE_NAME" - docker build . -t "$IMAGE_NAME"
- docker push "$IMAGE_NAME" - docker push "$IMAGE_NAME"
deploy:dev: deploy:dev:
stage: deploy image: docker:25-cli
image: docker:stable stage: deploy
only: only:
- develop - main
environment: environment:
name: dev name: dev
url: http://api.dev.sahkoinsinoorikilta.fi url: http://api.dev.sahkoinsinoorikilta.fi
variables: variables:
DOCKER_HOST: $DEV_CI_DOCKER_HOST DOCKER_HOST: $DEV_CI_DOCKER_HOST
DOCKER_TLS_VERIFY: 1 DOCKER_TLS_VERIFY: 1
before_script: before_script:
- mkdir -p ~/.docker - mkdir -p ~/.docker
- echo "$DEV_TLSCACERT" > ~/.docker/ca.pem - echo "$DEV_TLSCACERT" > ~/.docker/ca.pem
- echo "$DEV_TLSCERT" > ~/.docker/cert.pem - echo "$DEV_TLSCERT" > ~/.docker/cert.pem
- echo "$DEV_TLSKEY" > ~/.docker/key.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: script:
- docker stack deploy --with-registry-auth -c stack-compose-dev.yml "$SERVICE_NAME" - docker stack deploy --with-registry-auth -c stack-compose-dev.yml "$SERVICE_NAME"
after_script: after_script:
- docker logout "$CI_REGISTRY" - docker logout "$CI_REGISTRY"
deploy:production: deploy:production:
stage: deploy stage: deploy
image: docker:stable image: docker:25-cli
only: only:
- master - production
environment: environment:
name: production name: production
url: https://api.sahkoinsinoorikilta.fi url: https://api.sahkoinsinoorikilta.fi
when: manual when: manual
variables: variables:
DOCKER_HOST: $CI_DOCKER_HOST DOCKER_HOST: $CI_DOCKER_HOST
DOCKER_TLS_VERIFY: 1 DOCKER_TLS_VERIFY: 1
before_script: before_script:
- mkdir -p ~/.docker - mkdir -p ~/.docker
- echo "$TLSCACERT" > ~/.docker/ca.pem - echo "$TLSCACERT" > ~/.docker/ca.pem
- echo "$TLSCERT" > ~/.docker/cert.pem - echo "$TLSCERT" > ~/.docker/cert.pem
- echo "$TLSKEY" > ~/.docker/key.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: script:
- docker stack deploy --with-registry-auth -c stack-compose.yml "$SERVICE_NAME" - docker stack deploy --with-registry-auth -c stack-compose.yml "$SERVICE_NAME"
after_script: 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"
-1
View File
@@ -1 +0,0 @@
_
Executable → Regular
+1 -4
View File
@@ -1,10 +1,7 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
PURPLE='\033[0;35m' PURPLE='\033[0;35m'
NC='\033[0m' # No Color NC='\033[0m' # No Color
source "${VIRTUAL_ENV}/bin/activate" . "${VIRTUAL_ENV}/bin/activate"
if [ $? -ne 0 ] if [ $? -ne 0 ]
then then
+1 -4
View File
@@ -1,10 +1,7 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
PURPLE='\033[0;35m' PURPLE='\033[0;35m'
NC='\033[0m' # No Color NC='\033[0m' # No Color
source "${VIRTUAL_ENV}/bin/activate" . "${VIRTUAL_ENV}/bin/activate"
if [ $? -ne 0 ] if [ $? -ne 0 ]
then then
+1
View File
@@ -0,0 +1 @@
22.13.1
+1 -1
View File
@@ -1 +1 @@
3.9 3.12.9
+2
View File
@@ -0,0 +1,2 @@
python 3.12.9
poetry 2.1.1
+7 -6
View File
@@ -1,13 +1,13 @@
FROM python:3.9-slim-buster as builder FROM python:3.12.9-slim-bullseye AS builder
ENV PYTHONUNBUFFERED 1 ENV PYTHONUNBUFFERED 1
COPY . ./ COPY . ./
ENV POETRY_VERSION=2.1.1
ENV POETRY_VERSION=1.1.13 RUN pip install pip==25.3
RUN pip install "poetry==$POETRY_VERSION" RUN pip install "poetry==$POETRY_VERSION"
RUN poetry export --without-hashes > requirements.txt RUN poetry self add poetry-plugin-export
RUN poetry export --without-hashes --format=requirements.txt --output requirements.txt
FROM python:3.9-slim-buster as server FROM python:3.12.9-slim-bullseye AS server
WORKDIR /app WORKDIR /app
COPY . ./ COPY . ./
@@ -22,6 +22,7 @@ ENV PYTHONUNBUFFERED=1 \
PIP_DEFAULT_TIMEOUT=100 PIP_DEFAULT_TIMEOUT=100
RUN apt-get update && apt-get install --no-install-recommends -y build-essential RUN apt-get update && apt-get install --no-install-recommends -y build-essential
RUN pip install pip==25.3
RUN pip install --no-deps -r requirements.txt RUN pip install --no-deps -r requirements.txt
RUN python manage.py collectstatic --noinput RUN python manage.py collectstatic --noinput
+84 -54
View File
@@ -1,79 +1,107 @@
# SIKWEB 2.0 # Web 2.0 Backend
A modern web app using a Django backend and an Angular frontend. [Django](https://www.djangoproject.com/) backend containing multiple small applications and api for Next.js frontend.
## Components * **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.
### Infoscreen ## Installation
Angular-based slideshow app for the guild room's screens.
### Member register
Data table app for viewing and modifying the member register, member applications and membership payments.
### Web app
Mostly static website with an event calendar and news feed.
## Accessing the source
### Clone this repository and enter it
Set up your SSH key authentication in GitLab Profile Settings. Then clone the repository and checkout the development branch: Set up your SSH key authentication in GitLab Profile Settings. Then clone the repository and checkout the development branch:
```bash ```bash
git clone git@gitlab.com:sahkoinsinoorikilta/vtmk/web2.0-backend.git git clone git@gitlab.com:sahkoinsinoorikilta/vtmk/web2.0-backend.git
cd web2.0-backend cd web2.0-backend
git checkout develop
``` ```
## Development Copy env file for local use:
```bash
cp .env.dev .env
```
### Poetry ### Poetry
For depedencies and virtual environment, we use [poetry](https://python-poetry.org/). For depedencies and virtual environment, we use [poetry](https://python-poetry.org/).
```bash First install [python](https://wiki.python.org/moin/BeginnersGuide/Download). Then install poetry:
python3 -m pip install poetry
```
The easiest integration with VSCode is to have poetry install virtual environment in project folder, configured with CMD
```bash ```bash
python3 -m poetry config virtualenvs.in-project true python -m pip install poetry==2.1.1
``` ```
Start developing by install dependencies first Install dependencies with
#### CMDs
Activate virtual environment in shell
```bash
python3 -m poetry shell
```
Install dependencies
```bash ```bash
poetry install poetry install
``` ```
### npm scripts Poetry is configured to install dependencies in a virtual environment, so you should see `.venv` folder in repo root.
We use Node.js for few development tasks, like linting. Easiest way to install Node is [nvm](https://github.com/nvm-sh/nvm). Activate virtual environment in shell
TODO: List scripts ```bash
eval $(poetry env activate)
```
### Node
We use Node.js for few development tasks, like linting.
Easiest way to install Node is [nvm](https://github.com/nvm-sh/nvm).
After installing install dependencies:
```bash
npm install
```
See [Linting](#linting) for more info
### 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
Install dependencies with
```bash
poetry install
```
and make sure you are using Python from your virutal environment.
Virtual environment can be activated with
```bash
eval $(poetry env activate)
```
and you verify correct Python executable with
```bash
which python
# should return path similar to {your-system path}/web2.0-backend/.venv/bin/python
```
### Initializing data ### Initializing data
Run the following `manage.py` commands. Do not run these in production without thinking! Run the following `manage.py` commands to initialize a new database. Do not run these in production without thinking!
```bash ```bash
python manage.py createdefaultadmin # creates an admin user python manage.py migrate # run migrations
python manage.py initialize # creates user groups python manage.py createdefaultadmin # creates an admin user
python manage.py createdummydata # creates dummy members to the member register python manage.py initialize # creates user groups
python manage.py createdummydata # creates dummy members to the member register
``` ```
### Running ### Running
@@ -82,8 +110,6 @@ python manage.py createdummydata # creates dummy members to the member regis
python manage.py runserver python manage.py runserver
``` ```
#### Visit the page
Visit [https://localhost:8000](https://localhost:8000) in your browser! 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. Using address `0.0.0.0` will bind to all IP addresses. Using `localhost` will only bind to your machine.
@@ -99,27 +125,29 @@ When you start working on a feature, create a feature branch for your changes. T
Example of creating a feature branch: Example of creating a feature branch:
```bash ```bash
git checkout -b feature-error-page 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. When your changes are ready and the code works without errors, submit a merge request to `main` 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. Bugfixes do not need their own feature branches and can be pushed straight to `main`, 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`. Merge requests to `main` should be reviewed by multiple developers. Only a moderator can accept merge requests to `production`.
### Linting ### Linting
Lint python files using `pycodestyle` with Lint python files using `black` with
```bash ```bash
pycodestyle --config=pycodestyle.cfg --count . npm run lint:py # check changes
npm run lint:py:fix # fix errors
``` ```
Lint javascript and markdown using `eslint` and `remark` with Lint javascript and markdown using `eslint` and `remark` with
```bash ```bash
npm test 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_. Use an editor with linting capabilities to write pretty code that passes linting. Examples include _VSCode_, _Atom_ and _Pycharm_.
@@ -140,6 +168,8 @@ Tests are located in `tests.py` under every subproject.
Project is run in production with Docker. See `Dockerfile` for details. 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 ## 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`. 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 `main` or `production`.
+15 -7
View File
@@ -1,22 +1,30 @@
version: '3'
services: services:
db: db:
image: postgres:12 image: postgres:12
volumes: volumes:
- dbdata:/var/lib/postgresql/data - dbdata:/var/lib/postgresql/data
ports: ports:
- "5432:5432" - 5432:5432
environment: environment:
- POSTGRES_PASSWORD=postgres - POSTGRES_PASSWORD=postgres
web: web:
build: . build: .
image: registry.gitlab.com/sahkoinsinoorikilta/vtmk/web2.0-backend environment:
env_file: - DEPLOY_ENV=local
- .env - HOST=localhost
- DEBUG=True
- SECRET_KEY=7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp(
- DB_NAME=postgres
- DB_USER=postgres
- DB_PASSWD=postgres
- DB_HOST=db
- DB_PORT=5432
- EMAIL_API_KEY=
- GROUP_KEY=
- GOOGLE_CREDS='{}'
ports: ports:
- "8000:8000" - 8000:8000
depends_on: depends_on:
- db - db
+23
View File
@@ -0,0 +1,23 @@
import globals from "globals";
import js from "@eslint/js";
export default [
{
ignores: ["**/.venv/", "**/collected_static/", "**/static/js/lib/**"],
},
{
languageOptions: {
globals: {
...globals.browser,
...globals.jquery,
angular: true,
moment: true,
_: true
},
},
},
{
...js.configs.recommended
}
];
+1 -1
View File
@@ -7,7 +7,7 @@ from django import forms
from django.utils import timezone from django.utils import timezone
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType 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): class InfoItem(models.Model):
+23 -23
View File
@@ -1,6 +1,6 @@
"""File containing infoscreen urls.""" """File containing infoscreen urls."""
from django.conf.urls import url from django.urls import re_path
from django.conf import settings from django.conf import settings
from infoscreen.views import index from infoscreen.views import index
@@ -27,28 +27,28 @@ from infoscreen.views import createApyItem
from infoscreen.views import get_apy_json from infoscreen.views import get_apy_json
urlpatterns = [ urlpatterns = [
url(r"^$", default), re_path(r"^$", default),
url(r"^admin$", admin), re_path(r"^admin$", admin),
url(r"^(?P<idx>\d+)$", index), re_path(r"^(?P<idx>\d+)$", index),
url(r"^items$", info_items), re_path(r"^items$", info_items),
url(r"^rotation/(?P<idx>\d+)$", rotation), re_path(r"^rotation/(?P<idx>\d+)$", rotation),
url(r"^rotations$", rotations), re_path(r"^rotations$", rotations),
url(r"^instance$", createInstance), re_path(r"^instance$", createInstance),
url(r"^instance/(?P<idx>\d+)$", deleteInstance), re_path(r"^instance/(?P<idx>\d+)$", deleteInstance),
url(r"^types$", info_types), re_path(r"^types$", info_types),
url(r"^delete_item/(?P<type_id>\d+)/(?P<idx>\d+)$", delete_info_item), re_path(r"^delete_item/(?P<type_id>\d+)/(?P<idx>\d+)$", delete_info_item),
url(r"^create_external_image$", createExternalImageInfoItem), re_path(r"^create_external_image$", createExternalImageInfoItem),
url(r"^create_image$", create_image_item), re_path(r"^create_image$", create_image_item),
url(r"^create_video$", create_video_item), re_path(r"^create_video$", create_video_item),
url(r"^create_abbitem$", createABBItem), re_path(r"^create_abbitem$", createABBItem),
url(r"^create_sossoitem$", createSossoItem), re_path(r"^create_sossoitem$", createSossoItem),
url(r"^create_lunchitem$", createLunchItem), re_path(r"^create_lunchitem$", createLunchItem),
url(r"^create_eventitem$", createEventItem), re_path(r"^create_eventitem$", createEventItem),
url(r"^create_apyitem$", createApyItem), re_path(r"^create_apyitem$", createApyItem),
url(r"^create_websiteitem$", createExternalWebsiteItem), re_path(r"^create_websiteitem$", createExternalWebsiteItem),
url(r"^create_rotation$", create_rotation), re_path(r"^create_rotation$", create_rotation),
url(r"^delete_rotation/(?P<id>\d+)$", delete_rotation), re_path(r"^delete_rotation/(?P<id>\d+)$", delete_rotation),
url(r"^apyjson", get_apy_json), re_path(r"^apyjson", get_apy_json),
] ]
if settings.DEBUG: if settings.DEBUG:
+5 -5
View File
@@ -1,20 +1,20 @@
from django import forms 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 django.core.exceptions import ValidationError
from kaehmy.models import PresetRole, CustomRole, Application, Comment, BaseRole from kaehmy.models import PresetRole, CustomRole, Application, Comment, BaseRole
class CheckboxSelectMultiple(forms.widgets.CheckboxSelectMultiple): class CheckboxSelectMultiple(forms.widgets.CheckboxSelectMultiple):
option_template_name = "kaehmy/checkbox_option.html" option_template_name = "checkbox_option.html"
def create_option( def create_option(
self, name, value, label, selected, index, subindex=None, attrs=None self, name, formIterator, label, selected, index, subindex=None, attrs=None
): ):
dic = super(CheckboxSelectMultiple, self).create_option( dic = super(CheckboxSelectMultiple, self).create_option(
name, value, label, selected, index, subindex, attrs name, formIterator, label, selected, index, subindex, attrs
) )
description = PresetRole.objects.get(id=value).description description = PresetRole.objects.get(id=formIterator.value).description
dic["description"] = description dic["description"] = description
return dic return dic
@@ -0,0 +1,65 @@
# Generated by Django 4.2.24 on 2025-10-13 14:48
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("kaehmy", "0011_delete_kaehmybaserole"),
]
operations = [
migrations.AlterField(
model_name="baserole",
name="category",
field=models.CharField(
choices=[
("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"),
],
default="others",
max_length=255,
verbose_name="Category",
),
),
migrations.AlterField(
model_name="customrole",
name="baserole_ptr",
field=models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="kaehmy.baserole",
),
),
migrations.AlterField(
model_name="presetrole",
name="baserole_ptr",
field=models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="kaehmy.baserole",
),
),
]
+6 -3
View File
@@ -1,6 +1,6 @@
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
VERBOSE_NAME = _("Kaehmy") VERBOSE_NAME = _("Kaehmy")
@@ -13,18 +13,21 @@ class BaseRole(models.Model):
is_board = models.BooleanField(_("Board member")) is_board = models.BooleanField(_("Board member"))
CATEGORIES = ( CATEGORIES = (
("board", _("Board")),
("corporate", _("Corporate affairs")), ("corporate", _("Corporate affairs")),
("freshman", _("Freshmen")), ("freshman", _("Freshmen")),
("international", _("International")), ("international", _("International")),
("external", _("External affairs")), ("siwa", _("SIK's free time")),
("media", _("Media")), ("media", _("Media")),
("tech", _("Technology")), ("tech", _("Technology")),
("wellbeing", _("Wellbeing")), ("wellbeing", _("Wellbeing")),
("elepaja", _("Elepaja")), ("sikpaja", _("Sik-paja")),
("ceremonies", _("Ceremonies")), ("ceremonies", _("Ceremonies")),
("studies", _("Studies")), ("studies", _("Studies")),
("sosso", _("Sössö magazine")), ("sosso", _("Sössö magazine")),
("pota", _("PoTa")),
("alumni", _("Alumni relations")), ("alumni", _("Alumni relations")),
("n", _("N")),
("others", _("Others")), ("others", _("Others")),
) )
category = models.CharField( category = models.CharField(
+4 -6
View File
@@ -5,12 +5,6 @@
margin-right: auto; margin-right: auto;
} }
body {
max-width: 1000px;
margin-left: auto !important;
margin-right: auto !important;
}
div.tooltip-inner { div.tooltip-inner {
max-width: 25rem; max-width: 25rem;
} }
@@ -28,6 +22,10 @@ div.tooltip-inner {
.kaehmy-content { .kaehmy-content {
padding-left: 0.5rem; padding-left: 0.5rem;
padding-right: 0.5rem; padding-right: 0.5rem;
max-width: 1000px;
width: 100%;
margin-left: auto;
margin-right: auto;
} }
p { p {
+2 -5
View File
@@ -3,13 +3,9 @@
} }
footer { footer {
/* position: absolute; */
bottom: 0; bottom: 0;
width: 100%; width: 100%;
height: 60px; /* Set the fixed height of the footer here */ margin: 1rem;
/* line-height: 60px; /* Vertically center the text there */
margin-top: 2rem;
margin-bottom: 1rem;
} }
footer .container .col .nav .nav-item { footer .container .col .nav .nav-item {
@@ -26,6 +22,7 @@ footer .container .col .nav .nav-item {
.lang-select { .lang-select {
width: 10rem; width: 10rem;
margin-bottom: 1rem;
display: inline-block; 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; max-width: 1000px;
width: 100%;
margin-left: auto; margin-left: auto;
margin-right: 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 { .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 { .kaehmy_navigation {
margin-bottom: 10px; margin-bottom: 10px;
}
.navbar-border {
border-bottom: 2px solid #282b3b; border-bottom: 2px solid #282b3b;
} }
.navbar-light .navbar-nav .nav-link { .navbar-light .navbar-nav .nav-link {
color: black; color: black;
} }
.navbar {
max-width: 1000px;
width: 100%;
margin-left: auto;
margin-right: auto;
}
+1 -1
View File
@@ -1,6 +1,6 @@
import django_tables2 as tables import django_tables2 as tables
from django.db.models import Count, Q 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 from kaehmy.models import Application
+8 -8
View File
@@ -1,8 +1,8 @@
"""Kaehmy urls.""" """Kaehmy urls."""
from django.conf.urls import url from django.urls import re_path
from django.conf import settings 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 view
from kaehmy.views import list_view from kaehmy.views import list_view
@@ -13,12 +13,12 @@ from kaehmy.views import export_view
urlpatterns = [ urlpatterns = [
# kaehmy # kaehmy
url(r"^new", view), re_path(r"^new", view),
url(r"^submit", submit), re_path(r"^submit", submit),
url(r"^add_comment", comment), re_path(r"^add_comment", comment),
url(r"^statistics", statistics_view), re_path(r"^statistics", statistics_view),
url(r"^export", export_view), re_path(r"^export", export_view),
url(r"^$", list_view), re_path(r"^$", list_view),
] ]
if settings.DEBUG: if settings.DEBUG:
+11 -10
View File
@@ -4,6 +4,7 @@ from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.csrf import ensure_csrf_cookie
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.template.loader import render_to_string
import logging import logging
from sikweb.settings import URL from sikweb.settings import URL
@@ -64,14 +65,15 @@ def comment(request, *args, **kwargs):
if form.is_valid(): if form.is_valid():
comment = form.save() comment = form.save()
name = comment.name name = comment.name
url = f"https://{URL}/kaehmy"
to_email = comment.parent.email to_email = comment.parent.email
subject = "Kaehmyysi tai kommenttiisi on vastattu!" subject = "Kaehmyysi tai kommenttiisi on vastattu!"
email_body = ( message = render_to_string(
f"{name.capitalize()} on vastannut kaehmyhakemukseesi tai kommenttiisi kaehmypalvelussa.\r\n\r\n" "kaehmy/email_comment.html", {"name": name, "url": url}
"Käy lukemassa viesti osoitteessa https://{URL}/kaehmy"
) )
send_email(to=to_email, subject=subject, body=email_body)
send_email(to=to_email, subject=subject, body=message, html=True)
logging.debug(f"Sent kaehmy comment email to recipient <{to_email}>") logging.debug(f"Sent kaehmy comment email to recipient <{to_email}>")
return redirect("/kaehmy") return redirect("/kaehmy")
@@ -121,6 +123,7 @@ def submit(request, *args, **kwargs):
application = form.save() application = form.save()
custom_name = form.cleaned_data.get("custom_role_name") custom_name = form.cleaned_data.get("custom_role_name")
custom_is_board = form.cleaned_data.get("custom_role_is_board") custom_is_board = form.cleaned_data.get("custom_role_is_board")
kaehmybot_allowed = form.cleaned_data.get("kaehmybot") == "1"
if len(custom_name) > 0: 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)
@@ -129,16 +132,14 @@ def submit(request, *args, **kwargs):
url = f"https://{URL}/kaehmy" url = f"https://{URL}/kaehmy"
name = form.cleaned_data.get("name", "Anonymous") name = form.cleaned_data.get("name", "Anonymous")
email_body = (
f"Moikka {name}!\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 {url}"
)
to_email = form.cleaned_data.get("email", "") to_email = form.cleaned_data.get("email", "")
subject = "Arwokas kirjattu kirje mahdolliselle tulewalle kiltahenkilölle" subject = "Arwokas kirjattu kirje mahdolliselle tulewalle kiltahenkilölle"
message = render_to_string(
"kaehmy/email_kaehmy.html", {"name": name, "url": url}
)
send_email(to_email, subject, email_body) send_email(to=to_email, subject=subject, body=message, html=True)
logging.debug(f"Sent kaehmy email to recipient <{to_email}>") logging.debug(f"Sent kaehmy email to recipient <{to_email}>")
processHooks(message=f"Uusi New kaehmy! {name} -> {url}", eventType="kaehmy") processHooks(message=f"Uusi New kaehmy! {name} -> {url}", eventType="kaehmy")
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
+6 -1
View File
@@ -3,8 +3,13 @@
from django.contrib import admin from django.contrib import admin
from members.models import Member, Request, Payment from members.models import Member, Request, Payment
# Register your models here. # Register your models here.
admin.site.register(Member) class MemberAdmin(admin.ModelAdmin):
search_fields = ("first_name", "last_name", "email", "POR")
admin.site.register(Member, MemberAdmin)
admin.site.register(Request) admin.site.register(Request)
admin.site.register(Payment) admin.site.register(Payment)
+1 -1
View File
@@ -2,7 +2,7 @@
from django import forms from django import forms
from django.utils import timezone 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 from members.models import Member, Payment, Request
+1 -1
View File
@@ -2,7 +2,7 @@
from django.db import models from django.db import models
from django.utils import timezone 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 from django.db.models import Q, OuterRef, Subquery
+1 -1
View File
@@ -1,7 +1,7 @@
"""File containing member application django tables.""" """File containing member application django tables."""
import django_tables2 as 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.core.exceptions import ObjectDoesNotExist
from django.db.models import F, OuterRef, Subquery from django.db.models import F, OuterRef, Subquery
from django.utils import timezone from django.utils import timezone
+33 -33
View File
@@ -1,6 +1,6 @@
"""File containing Member application URLs.""" """File containing Member application URLs."""
from django.conf.urls import url from django.urls import re_path
from django.conf import settings from django.conf import settings
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required, permission_required
@@ -42,61 +42,61 @@ from members.views import application_submit
urlpatterns = [ urlpatterns = [
# landing page # landing page
url(r"^$", member_list), re_path(r"^$", member_list),
url(r"^list$", member_list), re_path(r"^list$", member_list),
# add member form view # add member form view
url(r"^add$", member_add), re_path(r"^add$", member_add),
# add many members view # add many members view
url(r"^add_many$", member_add_many), re_path(r"^add_many$", member_add_many),
# edit member information view # edit member information view
url(r"^edit/(?P<index>\d+)$", member_edit), re_path(r"^edit/(?P<index>\d+)$", member_edit),
# delete confirmation view # 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 # list all member applications
url(r"^applications$", application_list), re_path(r"^applications$", application_list),
# edit member application # 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 # post request targets
url(r"^submit_member$", member_submit), re_path(r"^submit_member$", member_submit),
url(r"^update_member$", member_update), re_path(r"^update_member$", member_update),
url(r"^delete_member$", member_delete), re_path(r"^delete_member$", member_delete),
url(r"^submit_payment$", payment_submit), re_path(r"^submit_payment$", payment_submit),
url(r"^update_payment$", payment_update), re_path(r"^update_payment$", payment_update),
url(r"^delete_payment$", payment_delete), re_path(r"^delete_payment$", payment_delete),
url(r"^submit_application$", application_submit), re_path(r"^submit_application$", application_submit),
url(r"^accept_application$", application_accept), re_path(r"^accept_application$", application_accept),
url(r"^delete_application$", application_delete), re_path(r"^delete_application$", application_delete),
# the actual member application form # the actual member application form
url(r"^application/$", application_form), re_path(r"^application/$", application_form),
# delete confirmation view for applications # 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 # list all payment events
url(r"^payments$", payment_list), re_path(r"^payments$", payment_list),
# add payment event # add payment event
url(r"^add_payment$", payment_add), re_path(r"^add_payment$", payment_add),
# edit payment event # 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 # 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 # 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 # settings page
url(r"^settings$", settings_page), re_path(r"^settings$", settings_page),
# send CSV member data by POST # send CSV member data by POST
url(r"^import_csv", import_csv), re_path(r"^import_csv", import_csv),
# export members as excel file # export members as excel file
url(r"export_members", export_members_excel), re_path(r"export_members", export_members_excel),
url(r"export_payments", export_payments_excel), re_path(r"export_payments", export_payments_excel),
url(r"export_applications", export_applications_excel), re_path(r"export_applications", export_applications_excel),
# rest api url # 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 # member select autocomplete view
url( re_path(
r"^member-autocomplete/$", r"^member-autocomplete/$",
MemberAutoComplete.as_view(), 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: if settings.DEBUG:
+1 -1
View File
@@ -1,4 +1,4 @@
"""File containing Members application views.""" """File containing Members application views."""
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
+1 -1
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.views.decorators.csrf import ensure_csrf_cookie
from django.http import HttpResponse, HttpResponseRedirect from django.http import HttpResponse, HttpResponseRedirect
from django.conf import settings 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.forms.models import model_to_dict
from django.template.loader import render_to_string from django.template.loader import render_to_string
+2 -2
View File
@@ -10,7 +10,7 @@ from django.http import (
HttpResponseForbidden, HttpResponseForbidden,
) )
from django.conf import settings 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.forms.models import model_to_dict
from dal import autocomplete from dal import autocomplete
from django.utils import timezone from django.utils import timezone
@@ -249,7 +249,7 @@ class MemberAutoComplete(autocomplete.Select2QuerySetView):
if self.q: if self.q:
qs = Member.find_members_by_name(self.q) qs = Member.find_members_by_name(self.q)
return qs return qs.order_by("last_name")
class CheckByEmail(APIView): class CheckByEmail(APIView):
+1 -1
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.views.decorators.csrf import ensure_csrf_cookie
from django.http import HttpResponse, HttpResponseRedirect from django.http import HttpResponse, HttpResponseRedirect
from django.conf import settings 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.forms.models import model_to_dict
import logging import logging
+2 -2
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.views.decorators.csrf import ensure_csrf_cookie
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest
from django.conf import settings 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.forms.models import model_to_dict
from django_tables2.config import RequestConfig from django_tables2.config import RequestConfig
@@ -135,7 +135,7 @@ def import_csv(request, *args, **kwargs):
member_table = MemberTable( member_table = MemberTable(
result.members, result.members,
request=request, request=request,
exclude=["id", "options"], exclude=["id", "options", "last_paid"],
attrs={"class": "table table-bordered table-hover"}, attrs={"class": "table table-bordered table-hover"},
) )
+1 -1
View File
@@ -1,7 +1,7 @@
"""File containing Ohlhafv forms.""" """File containing Ohlhafv forms."""
from django import forms 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 django.core.exceptions import ValidationError
from ohlhafv.models import OhlhafvChallenge from ohlhafv.models import OhlhafvChallenge
+1 -1
View File
@@ -5,7 +5,7 @@ from django.utils import timezone
from datetime import timedelta from datetime import timedelta
from django.contrib.auth.models import User from django.contrib.auth.models import User
from webapp.utils import month_from_now from webapp.utils import month_from_now
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.contrib.auth.models import User from django.contrib.auth.models import User
from auditlog.registry import auditlog from auditlog.registry import auditlog
from phonenumber_field.modelfields import PhoneNumberField from phonenumber_field.modelfields import PhoneNumberField
+1
View File
@@ -4,6 +4,7 @@
.navbar-border { .navbar-border {
border-bottom: 2px solid #282b3b; border-bottom: 2px solid #282b3b;
border-radius: 0px 0px 8px 8px;
} }
.navbar-light .navbar-nav .nav-link { .navbar-light .navbar-nav .nav-link {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

+1 -1
View File
@@ -1,6 +1,6 @@
import django_tables2 as tables import django_tables2 as tables
from django.db.models import Count, Q from django.db.models import Count, Q
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from ohlhafv.models import OhlhafvChallenge from ohlhafv.models import OhlhafvChallenge
+5 -5
View File
@@ -1,16 +1,16 @@
"""Ohlhafv urls.""" """Ohlhafv urls."""
from django.conf.urls import url from django.urls import re_path
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
from ohlhafv.views import * from ohlhafv.views import *
urlpatterns = [ urlpatterns = [
# ohlhafv # ohlhafv
url(r"^submit", ohlhafv_submit), re_path(r"^submit", ohlhafv_submit),
url(r"^list", ohlhafv_list), re_path(r"^list", ohlhafv_list),
url(r"^$", ohlhafv_view), re_path(r"^$", ohlhafv_view),
] ]
if settings.DEBUG: if settings.DEBUG:
+1 -1
View File
@@ -3,7 +3,7 @@ from django.shortcuts import render
from django.views.decorators.http import require_http_methods from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.csrf import ensure_csrf_cookie
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.template.loader import render_to_string from django.template.loader import render_to_string
from sikweb.settings import URL from sikweb.settings import URL
+4756 -3742
View File
File diff suppressed because it is too large Load Diff
+9 -7
View File
@@ -9,7 +9,7 @@
"lint:py": "black --diff --check .", "lint:py": "black --diff --check .",
"lint:py:fix": "black .", "lint:py:fix": "black .",
"lint:py-type": "pyright", "lint:py-type": "pyright",
"prepare": "husky install" "prepare": "husky"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -17,13 +17,15 @@
}, },
"author": "SIK ry", "author": "SIK ry",
"license": "ISC", "license": "ISC",
"dependencies": { "devDependencies": {
"eslint": "^7.28.0", "@eslint/js": "^9.20.0",
"husky": "^6.0.0", "eslint": "^9.20.0",
"globals": "^15.14.0",
"husky": "^9.1.7",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"pyright": "^1.1.149", "pyright": "^1.1.393",
"remark-cli": "^9.0.0", "remark-cli": "^12.0.1",
"remark-preset-lint-recommended": "^5.0.0" "remark-preset-lint-recommended": "^7.0.1"
}, },
"remarkConfig": { "remarkConfig": {
"plugins": [ "plugins": [
Generated
-1293
View File
File diff suppressed because it is too large Load Diff
+18 -3
View File
@@ -10,8 +10,23 @@ fi
if test -f "$DB_PASSWD_FILE"; then if test -f "$DB_PASSWD_FILE"; then
export DB_PASSWD=$(cat $DB_PASSWD_FILE) export DB_PASSWD=$(cat $DB_PASSWD_FILE)
fi fi
if test -f "$GOOGLE_CREDS_JSON"; then if test -f "$G_PRIVATE_KEY_ID_FILE"; then
export GOOGLE_CREDS_JSON=$(cat $GOOGLE_CRED_JSON_FILE) export G_PRIVATE_KEY_ID=$(cat $G_PRIVATE_KEY_ID_FILE)
fi
if test -f "$G_PRIVATE_KEY_FILE"; then
export G_PRIVATE_KEY="$(cat $G_PRIVATE_KEY_FILE)"
fi
if test -f "$G_CLIENT_EMAIL_FILE"; then
export G_CLIENT_EMAIL=$(cat $G_CLIENT_EMAIL_FILE)
fi
if test -f "$G_CLIENT_ID_FILE"; then
export G_CLIENT_ID=$(cat $G_CLIENT_ID_FILE)
fi
if test -f "$G_CLIENT_URL_FILE"; then
export G_CLIENT_URL=$(cat $G_CLIENT_URL_FILE)
fi
if test -f "$GROUP_KEY_FILE"; then
export GROUP_KEY=$(cat $GROUP_KEY_FILE)
fi fi
# Collect static files # Collect static files
@@ -24,4 +39,4 @@ python manage.py migrate
# Start server # Start server
echo "Django running on http://localhost:8000 in production mode" echo "Django running on http://localhost:8000 in production mode"
gunicorn -w 4 -b 0.0.0.0:8000 sikweb.wsgi gunicorn --log-level debug -w 4 -b 0.0.0.0:8000 sikweb.wsgi
+53 -37
View File
@@ -1,49 +1,65 @@
[tool.poetry] [project]
name = "web2.0-backend" authors = [
version = "0.1.0" {name = "Aarni Halinen", email = "aarni.halinen@sahkoinsinoorikilta.fi"},
]
description = "Backend for sahkoinsinoorikilta.fi" description = "Backend for sahkoinsinoorikilta.fi"
authors = ["Aarni Halinen aarni.halinen@sahkoinsinoorikilta.fi"] name = "web2.0-backend"
readme = "README.md"
requires-python = "~3.12"
version = "0.1.0"
[virtualenvs]
create = true
in-project = true
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.9" decorator = "^4.4.2"
decorator = "^4.0.9" Django = "^4.2.19"
Django = "^3.2.14"
requests = "^2.28.1"
django-cors-headers = "^3.13.0"
djangorestframework = "^3.12.4"
psycopg2-binary = "^2.9.3"
django-bootstrap3 = "^21.2"
django-tables2 = "^2.4.1"
django-modeltranslation = "^0.18.4"
django-auditlog = "^2.1.1"
django-phonenumber-field = {version = "^6.3.0", extras = ["phonenumbers"]}
django-autocomplete-light = "^3.4.1"
six = "^1.12.0"
pyexcel = "^0.5.14"
pyexcel-xlsx = "^0.5.8"
django-import-export = "^2.8.0"
openpyxl = "^2.6.4"
django-app-namespace-template-loader = "^0.4.1" django-app-namespace-template-loader = "^0.4.1"
django-filter = "^22.1" django-auditlog = "^2.1.1"
whitenoise = "^6.2.0" django-autocomplete-light = "^3.4.1"
jsonschema = "^4.9.0" django-bootstrap3 = "^21.2.0"
Markdown = "^3.2.2" django-cors-headers = "^3.13.0"
uWSGI = "^2.0.18" django-filter = "^22.1.0"
gunicorn = "^20.1.0" django-import-export = "^2.8.0"
Pillow = "^9.1.1" django-modeltranslation = "^0.18.4"
sendgrid = "^6.7.0" django-phonenumber-field = {version = "^6.4.0", extras = ["phonenumbers"]}
sentry-sdk = "^1.4.3"
django-polymorphic = "^3.1.0" django-polymorphic = "^3.1.0"
python-dotenv = "^0.20.0" django-tables2 = "^2.4.1"
djangorestframework-simplejwt = "^5.2.0" djangorestframework = "^3.12.4"
djangorestframework-simplejwt = "^5.5.0"
google-auth = "^2.9.1" google-auth = "^2.9.1"
google-api-python-client = "^2.54.0" google-api-python-client = "^2.54.0"
gunicorn = "^23.0.0"
jsonschema = "^4.9.0"
Markdown = "^3.2.2"
openpyxl = "^2.6.4"
Pillow = "^10.0.0"
psycopg2-binary = "^2.9.3"
pyexcel = "^0.7.0"
pyexcel-io = "^0.6.0"
pyexcel-xlsx = "^0.6.0"
python-dotenv = "^0.20.0"
requests = "^2.28.1"
sendgrid = "^6.7.0"
sentry-sdk = "^2.24.1"
six = "^1.12.0"
uWSGI = "^2.0.28"
whitenoise = "^6.2.0"
pyjwt = "^2.9.0"
setuptools = "^80.9.0"
[tool.poetry.dev-dependencies] [tool.poetry.group.dev.dependencies]
black = "^25.1.0"
coverage = "^6.4.2" coverage = "^6.4.2"
safety = "^2.1.1" safety = "^2.3.4"
black = "^22.6.0"
[tool.poetry]
package-mode = false
[tool.poetry.requires-plugins]
poetry-plugin-export = "^1.9"
[build-system] [build-system]
requires = ["poetry-core>=1.0.0"] requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api" build-backend = "poetry.core.masonry.api"
+1 -1
View File
@@ -12,5 +12,5 @@
"reportMissingImports": true, "reportMissingImports": true,
"reportMissingTypeStubs": false, "reportMissingTypeStubs": false,
"pythonVersion": "3.9" "pythonVersion": "3.12.9"
} }
+1 -1
View File
@@ -2,7 +2,7 @@ import os
import logging import logging
import datetime import datetime
from os.path import expanduser from os.path import expanduser
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+13 -1
View File
@@ -82,7 +82,19 @@ DATABASES = {
# Google api settings # Google api settings
GROUP_KEY = os.getenv("GROUP_KEY", "") GROUP_KEY = os.getenv("GROUP_KEY", "")
GOOGLE_SERVICE_ACCOUNT = json.loads(os.getenv("GOOGLE_CREDS_JSON", "{}"))
GOOGLE_CREDS = {
"type": "service_account",
"project_id": "web2-backend",
"private_key_id": os.getenv("G_PRIVATE_KEY_ID", ""),
"private_key": os.getenv("G_PRIVATE_KEY", ""),
"client_email": os.getenv("G_CLIENT_EMAIL", ""),
"client_id": os.getenv("G_CLIENT_ID", ""),
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": os.getenv("G_CLIENT_URL", ""),
}
# JWT authentication # JWT authentication
SIMPLE_JWT = { SIMPLE_JWT = {
+13 -28
View File
@@ -1,23 +1,6 @@
"""sikweb URL Configuration from django.urls import re_path, include
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/1.9/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
Including another URLconf
1. Add an import: from blog import urls as blog_urls
2. Import the include() function: from django.conf.urls import url, include
3. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls))
"""
from django.conf.urls import url
from django.contrib import admin from django.contrib import admin
from django.views.static import serve as static_serve from django.views.static import serve as static_serve
from django.conf.urls import include
from django.conf.urls.static import static from django.conf.urls.static import static
from django.conf import settings from django.conf import settings
from django.contrib.staticfiles import views as static_views from django.contrib.staticfiles import views as static_views
@@ -27,18 +10,20 @@ favicon_view = RedirectView.as_view(url="static/img/favicon.png", permanent=True
urlpatterns = [ urlpatterns = [
url(r"", include("webapp.urls")), re_path(r"", include("webapp.urls")),
url(r"^members/", include("members.urls")), re_path(r"^members/", include("members.urls")),
url(r"^infoscreen/", include("infoscreen.urls")), re_path(r"^infoscreen/", include("infoscreen.urls")),
url(r"^kaehmy/", include("kaehmy.urls")), re_path(r"^kaehmy/", include("kaehmy.urls")),
url(r"^ohlhafv/", include("ohlhafv.urls")), re_path(r"^ohlhafv/", include("ohlhafv.urls")),
# favourite icon # favourite icon
url(r"^favicon\.ico$", favicon_view), re_path(r"^favicon\.ico$", favicon_view),
# admin # admin
url(r"^admin/", admin.site.urls), re_path(r"^admin/", admin.site.urls),
# i18n default view for changing the active language # i18n default view for changing the active language
url(r"^i18n/", include("django.conf.urls.i18n")), re_path(r"^i18n/", include("django.conf.urls.i18n")),
# staticfiles default view for static files in development # staticfiles default view for static files in development
url(r"^static/(?P<path>.*)$", static_views.serve), re_path(r"^static/(?P<path>.*)$", static_views.serve),
url(r"^media/(?P<path>.*)$", static_serve, {"document_root": settings.MEDIA_ROOT}), re_path(
r"^media/(?P<path>.*)$", static_serve, {"document_root": settings.MEDIA_ROOT}
),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
+24
View File
@@ -29,15 +29,39 @@ services:
- FRONTEND_URL=dev.sahkoinsinoorikilta.fi - FRONTEND_URL=dev.sahkoinsinoorikilta.fi
- DEBUG=True - DEBUG=True
- EMAIL_API_KEY_FILE=/run/secrets/DJANGO_EMAIL_API_KEY - EMAIL_API_KEY_FILE=/run/secrets/DJANGO_EMAIL_API_KEY
- G_PRIVATE_KEY_ID_FILE=/run/secrets/BACKEND_G_PRIVATE_KEY_ID
- G_PRIVATE_KEY_FILE=/run/secrets/BACKEND_G_PRIVATE_KEY
- G_CLIENT_EMAIL_FILE=/run/secrets/BACKEND_G_CLIENT_EMAIL
- G_CLIENT_ID_FILE=/run/secrets/BACKEND_G_CLIENT_ID
- G_CLIENT_URL_FILE=/run/secrets/BACKEND_G_CLIENT_URL
- GROUP_KEY_FILE=/run/secrets/BACKEND_GROUP_KEY
- DB_HOST=db - DB_HOST=db
- DB_PORT=5432 - DB_PORT=5432
secrets: secrets:
- DJANGO_EMAIL_API_KEY - DJANGO_EMAIL_API_KEY
- BACKEND_G_PRIVATE_KEY_ID
- BACKEND_G_PRIVATE_KEY
- BACKEND_G_CLIENT_EMAIL
- BACKEND_G_CLIENT_ID
- BACKEND_G_CLIENT_URL
- BACKEND_GROUP_KEY
secrets: secrets:
DJANGO_EMAIL_API_KEY: DJANGO_EMAIL_API_KEY:
external: true external: true
BACKEND_G_PRIVATE_KEY_ID:
external: true
BACKEND_G_PRIVATE_KEY:
external: true
BACKEND_G_CLIENT_EMAIL:
external: true
BACKEND_G_CLIENT_ID:
external: true
BACKEND_G_CLIENT_URL:
external: true
BACKEND_GROUP_KEY:
external: true
volumes: volumes:
dbdata: dbdata:
+25 -4
View File
@@ -34,13 +34,24 @@ services:
- SECRET_KEY_FILE=/run/secrets/BACKEND_SECRET_KEY - SECRET_KEY_FILE=/run/secrets/BACKEND_SECRET_KEY
- DB_PASSWD_FILE=/run/secrets/BACKEND_DB_PASSWD - DB_PASSWD_FILE=/run/secrets/BACKEND_DB_PASSWD
- EMAIL_API_KEY_FILE=/run/secrets/BACKEND_EMAIL_API_KEY - EMAIL_API_KEY_FILE=/run/secrets/BACKEND_EMAIL_API_KEY
- GOOGLE_CREDS_JSON=/run/secrets/GOOGLE_CREDS_JSON - G_PRIVATE_KEY_ID_FILE=/run/secrets/BACKEND_G_PRIVATE_KEY_ID
- G_PRIVATE_KEY_FILE=/run/secrets/BACKEND_G_PRIVATE_KEY
- G_CLIENT_EMAIL_FILE=/run/secrets/BACKEND_G_CLIENT_EMAIL
- G_CLIENT_ID_FILE=/run/secrets/BACKEND_G_CLIENT_ID
- G_CLIENT_URL_FILE=/run/secrets/BACKEND_G_CLIENT_URL
- GROUP_KEY_FILE=/run/secrets/BACKEND_GROUP_KEY
secrets: secrets:
- BACKEND_SECRET_KEY - BACKEND_SECRET_KEY
- BACKEND_DB_PASSWD - BACKEND_DB_PASSWD
- BACKEND_EMAIL_API_KEY - BACKEND_EMAIL_API_KEY
- GOOGLE_CREDS_JSON - BACKEND_G_PRIVATE_KEY_ID
- BACKEND_G_PRIVATE_KEY
- BACKEND_G_CLIENT_EMAIL
- BACKEND_G_CLIENT_ID
- BACKEND_G_CLIENT_URL
- BACKEND_GROUP_KEY
secrets: secrets:
BACKEND_SECRET_KEY: BACKEND_SECRET_KEY:
external: true external: true
@@ -48,5 +59,15 @@ secrets:
external: true external: true
BACKEND_EMAIL_API_KEY: BACKEND_EMAIL_API_KEY:
external: true external: true
GOOGLE_CREDS_JSON: BACKEND_G_PRIVATE_KEY_ID:
EXTERNAL: true external: true
BACKEND_G_PRIVATE_KEY:
external: true
BACKEND_G_CLIENT_EMAIL:
external: true
BACKEND_G_CLIENT_ID:
external: true
BACKEND_G_CLIENT_URL:
external: true
BACKEND_GROUP_KEY:
external: true
+1 -1
View File
@@ -13,7 +13,7 @@
{% block body %} {% block body %}
{% block header %} {% block header %}
<div class="kaehmy_header"> <div class="kaehmy-header">
{% include "kaehmy/header.html" %} {% include "kaehmy/header.html" %}
</div> </div>
{% endblock header %} {% endblock header %}
+10
View File
@@ -0,0 +1,10 @@
{% load i18n %}
<p>
Hei!
</p>
<p>
{{ name }} on vastannut kaehmyhakemukseesi tai kommenttiisi kaehmypalvelussa.
Käy lukemassa viesti
<a href={{ url }}>täältä.</a>
</p>
+13
View File
@@ -0,0 +1,13 @@
{% load i18n %}
<p>
Moikka {{ name }}!
</p>
<p>
Hienoa, että kilta kiinnostaa! Kaehmysi on vastaanotettu.
Mahdollisista kommenteista tulee ilmoitus sähköpostitse.
</p>
<p>
Käy katsomassa kaehmytilanne
<a href={{ url }}>täältä.</a>
</p>
+1 -1
View File
@@ -1,4 +1,4 @@
{% extends "base.html" %} {% extends "kaehmy/base.html" %}
{% load static %} {% load static %}
{% load i18n %} {% load i18n %}
+9 -2
View File
@@ -1,7 +1,14 @@
{% load i18n %} {% load i18n %}
<div class="kaehmy_header-content"> <div class="kaehmy-header-content center">
<div class="kaehmy-banner logo"> <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> <a href="/kaehmy">
<img class="kaehmy-banner-image" src="https://static.sahkoinsinoorikilta.fi/logot-ja-grafiikka/web/side/SIK_RGB_W_side.png" alt="Aalto-yliopiston Sähköinsinöörikilta ry">
</a>
</div>
<div class="kaehmy-banner heading">
<p style="color:#D57A2D; font-size:2rem">{% blocktrans %}Kähmyt ovat auki!{% endblocktrans %}</p>
<p style="color:#BFDBD9; font-size:1rem">{% blocktrans %}Haku hallitukseen 23.10. mennessä ja toimihenkilöksi 15.11 mennessä.{% endblocktrans %}</p>
<p style="color:#BFDBD9; font-size:1.5rem">{% blocktrans %}Hae nyt!{% endblocktrans %}</p>
</div> </div>
</div> </div>
+13 -5
View File
@@ -28,10 +28,12 @@
</p> </p>
<h5>{% trans "Päivämääriä & deadlineja" %}</h5> <h5>{% trans "Päivämääriä & deadlineja" %}</h5>
<ul> <ul>
<li><strong>25.10.</strong> {% blocktrans %}Vaalikokous, osa 1 (puheenjohtajan valinta) ja hallitustyrkkypaneeli{% endblocktrans %}</li> <li><strong>11.10.</strong> {% blocktrans %}Toimikuntablää$t @Kiltis{% endblocktrans %}</li>
<li><strong>01.11.</strong> {% blocktrans %}Vaalikokous, osa 2 (hallituksen valinta){% endblocktrans %}</li> <li><strong>23.10.</strong> {% blocktrans %}Deadline hallitusvirkoihin hakemiselle.{% endblocktrans %}</li>
<li><strong>09.11.</strong> {% blocktrans %}Toimikunta-appro{% endblocktrans %}</li> <li><strong>24.10.</strong> {% blocktrans %}Vaalikokous, osa 1 (puheenjohtajan valinta) ja hallitustyrkkypaneeli{% endblocktrans %}</li>
<li><strong>17.11.</strong> {% blocktrans %}Vaalikokous, osa 3 (toimarien valinta){% endblocktrans %}</li> <li><strong>6.11.</strong> {% blocktrans %}Vaalikokous, osa 2 (hallituksen ja toimikuntien puheenjohtajien valinta){% endblocktrans %}</li>
<li><strong>15.11.</strong> {% blocktrans %}Deadline toimivirkoihin hakemiselle.{% endblocktrans %}</li>
<li><strong>21.11.</strong> {% blocktrans %}Vaalikokous, osa 3 (toimarien valinta){% endblocktrans %}</li>
</ul> </ul>
<form name="kaehmyForm" action="/kaehmy/submit/" method="post" class="form">{% csrf_token %} <form name="kaehmyForm" action="/kaehmy/submit/" method="post" class="form">{% csrf_token %}
{% bootstrap_field form.name %} {% bootstrap_field form.name %}
@@ -75,7 +77,13 @@
<input type="checkbox" required name="gdpr" value="1"> <input type="checkbox" required name="gdpr" value="1">
<span>{% blocktrans %} <span>{% blocktrans %}
Hyväksyn <a href="https://static.sahkoinsinoorikilta.fi/GDPR/Tietosuojaseloste%20%23U2013%20Toimihenkil%23U00f6ksi%20hakemisen%20rekisteri.pdf" target="_blank">tietosuojaselosteen</a> ja tietojeni tallentamisen. Hyväksyn <a href="https://static.sahkoinsinoorikilta.fi/GDPR/Suomeksi/Tietosuojaseloste%20%20Toimihenkilksi%20hakemisen%20rekisteri.pdf" target="_blank">tietosuojaselosteen</a> ja tietojeni tallentamisen.
{% endblocktrans %}
</span>
<br>
<input type="checkbox" name="kaehmybot" value="1" checked>
<span>{% blocktrans %}
Kähmybot saa lähettää hakemuksestani ilmoituksen killan telegramiin (hallitusvirkoihin hakiessa valitse kyllä).
{% endblocktrans %} {% endblocktrans %}
</span> </span>
{% buttons %} {% buttons %}
+1 -1
View File
@@ -2,7 +2,7 @@
<div class="card" style="margin-top: 0.5rem; margin-bottom: 0"> <div class="card" style="margin-top: 0.5rem; margin-bottom: 0">
<div class="card-block"> <div class="card-block">
<h4>{{ message.name }}</h4> <h4>{{ message.name }}</h4>
<p>{{ message.message|linebreaks|urlize }}</p> <p>{{ message.message|linebreaks|urlize }}</p>
<h6 class="card-subtitle mb-2 text-muted">{{ message.timestamp }}</h6> <h6 class="card-subtitle mb-2 text-muted">{{ message.timestamp }}</h6>
+2 -2
View File
@@ -1,8 +1,8 @@
{% load i18n %} {% load i18n %}
{% load static %} {% load static %}
<div class="kaehmy_navigation"> <div class="kaehmy_navigation bg-faded">
<nav class="navbar-border navbar navbar-toggleable-md navbar-light bg-faded"> <nav class="navbar navbar-toggleable-md navbar-light">
<div class="navbar-nav"> <div class="navbar-nav">
<a class="nav-item nav-link" href="/kaehmy">{% trans "List kaehmys" %}</a> <a class="nav-item nav-link" href="/kaehmy">{% trans "List kaehmys" %}</a>
<a class="nav-item nav-link" href="/kaehmy/new">{% trans "New kaehmy" %} <span class="sr-only">(current)</span></a> <a class="nav-item nav-link" href="/kaehmy/new">{% trans "New kaehmy" %} <span class="sr-only">(current)</span></a>
+1 -1
View File
@@ -7,5 +7,5 @@
<link rel="stylesheet" href="{% static "css/application.css" %}"> <link rel="stylesheet" href="{% static "css/application.css" %}">
<h3>{% trans "Hienoa! Jäsenhakemuksesi on nyt lähetetty." %}</h3> <h3>{% trans "Hienoa! Jäsenhakemuksesi on nyt lähetetty." %}</h3>
<p>{% trans "Vahvistusviesti on lähetetty sähköpostiisi. Ota yhteyttä admin@sahkoinsinoorikilta.fi jos viestiä ei näy." %}</p> <p>{% trans "Vahvistusviesti on lähetetty sähköpostiisi. Ota yhteyttä admin@sahkoinsinoorikilta.fi jos viestiä ei näy." %}</p>
<a href="https://sahkoinsinoorikilta.fi/"><h4>{% trans "Takaisin Sähköinsinöörikillan web-sivuille" %}</h4></a> <a href="https://sahkoinsinoorikilta.fi/"><h4>{% trans "Takaisin Sähköinsinöörikillan etusivulle" %}</h4></a>
{% endblock content %} {% endblock content %}
@@ -9,7 +9,5 @@
tulevia tapahtumia ja piipahda kiltahuoneella tutustumassa uusiin kiltatovereihisi! tulevia tapahtumia ja piipahda kiltahuoneella tutustumassa uusiin kiltatovereihisi!
</p> </p>
<p>Liity myös killan TG-kanaville:</p> <p>Liity myös killan TG-kanavalle:</p>
<p><a href="https://t.me/+ubTeGSYKTvg3NmVk">Killan yleinen telegram</a></p> <p><a href="https://t.me/+AB-JMbAxM2c0MDc0">Killan yleinen telegram</a></p>
<p><a href="https://t.me/+1PqQHRVMjiAxMTU0">SIK-fuksit 2022</a></p>
<p><a href="https://t.me/+Ln8TvQ-_id9kZTU0">SIK-fuksit 2022 -tiedotuskanava</a></p>
+1 -1
View File
@@ -1,4 +1,4 @@
{% extends "base.html" %} {% extends "members/base.html" %}
{% load static %} {% load static %}
{% load i18n %} {% load i18n %}
+2 -2
View File
@@ -5,6 +5,6 @@
{{ challenge.message }} {{ challenge.message }}
{% trans "Muistattehan vahvistaa haasteen paikan päällä Smökissä torstaina 26.5" %}. {% trans "Muistattehan vahvistaa haasteen paikan päällä Smökissä torstaina 12.2" %}.
{% trans "Käy kurkkaamassa muutkin haasteet osoitteessa" %} {{ url }} {% trans "Käy kurkkaamassa muutkin haasteet osoitteessa" %} {{ url }}
+1 -1
View File
@@ -3,6 +3,6 @@
<div class="ohlhafv-header-content"> <div class="ohlhafv-header-content">
<div class="ohlhafv-banner logo"> <div class="ohlhafv-banner logo">
<a href="/ohlhafv"><img class="ohlhafv-banner-image" src="{% static "ohlhafv/img/heevit.jpg" %}" alt="Aalto-yliopiston Sähköinsinöörikilta ry"></a> <a href="/ohlhafv"><img class="ohlhafv-banner-image" src="{% static "ohlhafv/img/heevi_banner.svg" %}" alt="Aalto-yliopiston Sähköinsinöörikilta ry"></a>
</div> </div>
</div> </div>
+1 -1
View File
@@ -6,4 +6,4 @@
<a href={{ url }}>{{url}}</a> <a href={{ url }}>{{url}}</a>
<p>Hädässä ota yhteyttä admin@sahkoinsinoorikilta.fi</p> <p>Hädässä ota yhteyttä tapahtuman järjestään.</p>
@@ -1,35 +0,0 @@
# Generated by Django 3.2.15 on 2022-08-06 14:33
from django.db import migrations, models
import django.utils.timezone
import webapp.utils
class Migration(migrations.Migration):
dependencies = [
("webapp", "0082_delete_baserole"),
]
operations = [
migrations.AddField(
model_name="basefeed",
name="base_autohide",
field=models.DateTimeField(default=webapp.utils.month_from_now),
),
migrations.AddField(
model_name="basefeed",
name="base_autohide_enabled",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="basefeed",
name="base_deleted",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="basefeed",
name="base_publish_time",
field=models.DateTimeField(default=django.utils.timezone.now),
),
]
@@ -0,0 +1,32 @@
# Generated by Django 4.2.24 on 2025-10-13 14:48
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
("contenttypes", "0002_remove_content_type_name"),
("webapp", "0082_delete_baserole"),
]
operations = [
migrations.AddField(
model_name="signup",
name="submit_id",
field=models.UUIDField(default=uuid.uuid4, editable=False, null=True),
),
migrations.AlterField(
model_name="basewebhook",
name="polymorphic_ctype",
field=models.ForeignKey(
editable=False,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="polymorphic_%(app_label)s.%(class)s_set+",
to="contenttypes.contenttype",
),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 4.2.24 on 2025-10-13 15:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("webapp", "0083_signup_submit_id_alter_basewebhook_polymorphic_ctype"),
]
operations = [
migrations.AlterField(
model_name="signup",
name="submit_id",
field=models.UUIDField(null=True),
),
]
@@ -1,31 +0,0 @@
# Generated by Django 2.2.28 on 2022-07-26 18:12
from django.db import migrations
def copyOldDataToNewFields(apps, schema_editor):
Event = apps.get_model("webapp", "Event")
Feed = apps.get_model("webapp", "Feed")
for event in Event.objects.all():
event.base_deleted = event.deleted
event.save()
for post in Feed.objects.all():
post.base_deleted = post.deleted
post.base_publish_time = post.publish_time
post.base_autohide = post.autohide
post.base_autohide_enabled = post.autohide_enabled
post.save()
class Migration(migrations.Migration):
dependencies = [
("webapp", "0083_auto_20220806_1733"),
]
operations = [
migrations.RunPython(
copyOldDataToNewFields, reverse_code=migrations.RunPython.noop
),
]
@@ -0,0 +1,18 @@
# Generated by Django 4.2.24 on 2025-10-13 15:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("webapp", "0084_alter_signup_submit_id"),
]
operations = [
migrations.AlterField(
model_name="signup",
name="submit_id",
field=models.CharField(null=True),
),
]
@@ -1,58 +0,0 @@
# Generated by Django 2.2.28 on 2022-07-26 18:28
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("webapp", "0084_auto_20220726_2112"),
]
operations = [
migrations.RenameField(
model_name="event",
old_name="deleted",
new_name="old_deleted",
),
migrations.RenameField(
model_name="feed",
old_name="autohide",
new_name="old_autohide",
),
migrations.RenameField(
model_name="feed",
old_name="autohide_enabled",
new_name="old_autohide_enabled",
),
migrations.RenameField(
model_name="feed",
old_name="deleted",
new_name="old_deleted",
),
migrations.RenameField(
model_name="feed",
old_name="publish_time",
new_name="old_publish_time",
),
migrations.RenameField(
model_name="basefeed",
old_name="base_autohide",
new_name="autohide",
),
migrations.RenameField(
model_name="basefeed",
old_name="base_autohide_enabled",
new_name="autohide_enabled",
),
migrations.RenameField(
model_name="basefeed",
old_name="base_deleted",
new_name="deleted",
),
migrations.RenameField(
model_name="basefeed",
old_name="base_publish_time",
new_name="publish_time",
),
]
@@ -0,0 +1,18 @@
# Generated by Django 4.2.24 on 2025-10-13 15:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("webapp", "0085_alter_signup_submit_id"),
]
operations = [
migrations.AlterField(
model_name="signup",
name="submit_id",
field=models.UUIDField(editable=False, null=True),
),
]
@@ -1,33 +0,0 @@
# Generated by Django 2.2.28 on 2022-07-26 18:29
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("webapp", "0085_auto_20220726_2128"),
]
operations = [
migrations.RemoveField(
model_name="event",
name="old_deleted",
),
migrations.RemoveField(
model_name="feed",
name="old_autohide",
),
migrations.RemoveField(
model_name="feed",
name="old_autohide_enabled",
),
migrations.RemoveField(
model_name="feed",
name="old_deleted",
),
migrations.RemoveField(
model_name="feed",
name="old_publish_time",
),
]
@@ -0,0 +1,18 @@
# Generated by Django 4.2.24 on 2025-10-13 15:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("webapp", "0086_alter_signup_submit_id"),
]
operations = [
migrations.AlterField(
model_name="signup",
name="submit_id",
field=models.UUIDField(editable=False, null=True, unique=True),
),
]
@@ -1,17 +0,0 @@
# Generated by Django 2.2.28 on 2022-07-26 19:26
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("webapp", "0086_auto_20220726_2129"),
]
operations = [
migrations.RenameModel(
old_name="JobAd",
new_name="RemoveJobAd",
),
]
-37
View File
@@ -1,37 +0,0 @@
# Generated by Django 2.2.28 on 2022-07-26 19:49
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
("webapp", "0087_auto_20220726_2226"),
]
operations = [
migrations.CreateModel(
name="JobAd",
fields=[
(
"basefeed_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="webapp.BaseFeed",
),
),
("created_at", models.DateTimeField(default=django.utils.timezone.now)),
],
options={
"verbose_name": "JobAd",
"verbose_name_plural": "JobAds",
},
bases=("webapp.basefeed",),
),
]
@@ -1,35 +0,0 @@
# Generated by Django 2.2.28 on 2022-07-26 19:29
from django.db import migrations
def copyOldDataToNewFields(apps, schema_editor):
Old = apps.get_model("webapp", "RemoveJobAd")
New = apps.get_model("webapp", "JobAd")
for jobAd in Old.objects.all():
New.objects.create(
id=jobAd.id,
title=jobAd.title,
tags=jobAd.tags,
visible=jobAd.visible,
deleted=jobAd.deleted,
publish_time=jobAd.publish_time,
autohide=jobAd.autohide_at,
autohide_enabled=jobAd.autohide_enabled,
description=jobAd.description,
content=jobAd.content,
created_at=jobAd.created_at,
)
class Migration(migrations.Migration):
dependencies = [
("webapp", "0088_jobad"),
]
operations = [
migrations.RunPython(
copyOldDataToNewFields, reverse_code=migrations.RunPython.noop
),
]
@@ -1,16 +0,0 @@
# Generated by Django 2.2.28 on 2022-07-26 19:52
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("webapp", "0089_auto_20220726_2229"),
]
operations = [
migrations.DeleteModel(
name="RemoveJobAd",
),
]
@@ -1,17 +0,0 @@
# Generated by Django 2.2.28 on 2022-07-26 20:11
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("webapp", "0090_delete_removejobad"),
]
operations = [
migrations.RemoveField(
model_name="jobad",
name="created_at",
),
]
@@ -1,33 +0,0 @@
# Generated by Django 2.2.28 on 2022-07-26 21:16
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("webapp", "0091_remove_jobad_created_at"),
]
operations = [
migrations.RenameField(
model_name="basefeed",
old_name="autohide_enabled",
new_name="autoUnpublish",
),
migrations.RenameField(
model_name="basefeed",
old_name="visible",
new_name="isPublished",
),
migrations.RenameField(
model_name="basefeed",
old_name="publish_time",
new_name="publishAt",
),
migrations.RenameField(
model_name="basefeed",
old_name="autohide",
new_name="unpublishAt",
),
]
@@ -1,18 +0,0 @@
# Generated by Django 2.2.28 on 2022-07-26 21:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("webapp", "0092_auto_20220727_0016"),
]
operations = [
migrations.AlterField(
model_name="basefeed",
name="isPublished",
field=models.BooleanField(default=False),
),
]
+125 -78
View File
@@ -10,7 +10,7 @@ from django.dispatch import receiver
import requests import requests
from uuid import uuid4 from uuid import uuid4
import logging import logging
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.db.models import JSONField from django.db.models import JSONField
from auditlog.registry import auditlog from auditlog.registry import auditlog
from polymorphic.models import PolymorphicModel from polymorphic.models import PolymorphicModel
@@ -24,15 +24,15 @@ EMAIL_REGEX = r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"
class Tag(models.Model): class Tag(models.Model):
"""Model for tag.""" """Model for tag."""
class Meta:
verbose_name = _("Tag")
verbose_name_plural = _("Tags")
id = models.AutoField(primary_key=True) id = models.AutoField(primary_key=True)
slug = models.SlugField(unique=True) slug = models.SlugField(unique=True)
name = models.CharField(max_length=127) name = models.CharField(max_length=127)
icon = models.ImageField() icon = models.ImageField()
class Meta:
verbose_name = _("Tag")
verbose_name_plural = _("Tags")
def __str__(self): def __str__(self):
return _("Tag: {}").format(self.slug) return _("Tag: {}").format(self.slug)
@@ -41,85 +41,89 @@ class BaseFeed(models.Model):
"""Model containing something showing on some info feed.""" """Model containing something showing on some info feed."""
id = models.AutoField(primary_key=True) id = models.AutoField(primary_key=True)
deleted = models.BooleanField(default=False) tags = models.ManyToManyField(Tag, related_name="feeds", blank=True)
visible = models.BooleanField(default=True)
title = models.CharField(max_length=255) title = models.CharField(max_length=255)
description = models.CharField(max_length=255) description = models.CharField(max_length=255)
content = models.TextField() content = models.TextField()
image = models.ImageField(blank=True, null=True) image = models.ImageField(blank=True, null=True)
tags = models.ManyToManyField(Tag, related_name="feeds", blank=True)
# Require explicit publishing from creator
isPublished = models.BooleanField(default=False)
# Automatically publish after this time, unless still in draft (!isPublished)
publishAt = models.DateTimeField(default=timezone.now)
autoUnpublish = models.BooleanField(default=False)
# Automatically unpublish after this if auto_unpublish==True
unpublishAt = models.DateTimeField(default=month_from_now)
webhookUrl = ""
hookType = ""
wasPublishedBefore = False
def __init__(self, *args, **kwargs):
super(BaseFeed, self).__init__(*args, **kwargs)
self.wasPublishedBefore = self.isPublished
def __str__(self):
delete_str = _("Deleted: ") if self.deleted else ""
return _("{}{}: {}").format(delete_str, self._meta.verbose_name, self.title)
def save(self, force_insert=False, force_update=False, *args, **kwargs):
created = self.pk is None
super(BaseFeed, self).save(force_insert, force_update, *args, **kwargs)
if self.isPublished and (created or not self.wasPublishedBefore):
self.refresh_from_db() # Fetch so we can use primary key
url = f"{self.webhookUrl}/{self.pk}"
processHooks(
message=generateMessage(
f"Uusi {self._meta.verbose_name}", self.title, self.description, url
),
eventType=self.hookType,
)
self.wasPublishedBefore = self.isPublished
class Feed(BaseFeed): class Feed(BaseFeed):
"""Model representing feed.""" """Model representing feed."""
webhookUrl = f"https://{FRONTEND_URL}/feed"
hookType = "feed"
class Meta: class Meta:
verbose_name = _("Feed") verbose_name = _("Feed")
verbose_name_plural = _("Feeds") verbose_name_plural = _("Feeds")
publish_time = models.DateTimeField(default=timezone.now)
autohide = models.DateTimeField(default=month_from_now)
autohide_enabled = models.BooleanField(default=False)
deleted = models.BooleanField(default=False)
def __str__(self):
delete_str = _("Deleted: ") if self.deleted else ""
return _("{}Feed: {}").format(delete_str, self.title)
__previousVisible = False
def __init__(self, *args, **kwargs):
super(Feed, self).__init__(*args, **kwargs)
self.__previousVisible = self.visible
def save(self, force_insert=False, force_update=False, *args, **kwargs):
created = self.pk is None
super(Feed, self).save(force_insert, force_update, *args, **kwargs)
if self.visible and (created or not self.__previousVisible):
self.refresh_from_db() # Fetch so we can use primary key
url = f"https://{FRONTEND_URL}/feed/{self.pk}"
processHooks(
message=generateMessage(
"Uusi uutinen", self.title, self.description, url
),
eventType="feed",
)
self.__previousVisible = self.visible
class Event(BaseFeed): class Event(BaseFeed):
"""Model for event in guild calendar""" """Model for event in guild calendar"""
start_time = models.DateTimeField(default=timezone.now)
end_time = models.DateTimeField(default=timezone.now)
location = models.CharField(max_length=255, blank=True)
signupForm = models.ManyToManyField("SignupForm", blank=True, related_name="event")
webhookUrl = f"https://{FRONTEND_URL}/events"
hookType = "event"
class Meta: class Meta:
verbose_name = _("Event") verbose_name = _("Event")
verbose_name_plural = _("Events") verbose_name_plural = _("Events")
start_time = models.DateTimeField(default=timezone.now)
end_time = models.DateTimeField(default=timezone.now)
signupForm = models.ManyToManyField("SignupForm", blank=True, related_name="event")
location = models.CharField(max_length=255, blank=True)
deleted = models.BooleanField(default=False)
class JobAd(BaseFeed): def __str__(self):
"""Job advertisements shown on Corporate relations page""" delete_str = _("Deleted: ") if self.deleted else ""
return _("{}Event: {}").format(delete_str, self.title)
webhookUrl = f"https://{FRONTEND_URL}/jobads" __previousVisible = False
hookType = "jobad"
class Meta: def __init__(self, *args, **kwargs):
verbose_name = _("JobAd") super(Event, self).__init__(*args, **kwargs)
verbose_name_plural = _("JobAds") self.__previousVisible = self.visible
def save(self, force_insert=False, force_update=False, *args, **kwargs):
created = self.pk is None
super(Event, self).save(force_insert, force_update, *args, **kwargs)
if self.visible and (created or not self.__previousVisible):
self.refresh_from_db() # Fetch so we can use primary key
url = f"https://{FRONTEND_URL}/events/{self.pk}"
processHooks(
message=generateMessage(
"Uusi tapahtuma", self.title, self.description, url
),
eventType="event",
)
self.__previousVisible = self.visible
class TemplateQuestion(models.Model): class TemplateQuestion(models.Model):
@@ -127,15 +131,15 @@ class TemplateQuestion(models.Model):
Stores template questions for signup forms as JSON format. Used in signup form creation. Stores template questions for signup forms as JSON format. Used in signup form creation.
""" """
class Meta:
verbose_name = _("Template question")
verbose_name_plural = _("Template questions")
id = models.AutoField(primary_key=True) id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
questions = JSONField() questions = JSONField()
deleted = models.BooleanField(default=False) deleted = models.BooleanField(default=False)
class Meta:
verbose_name = _("Template question")
verbose_name_plural = _("Template questions")
def __str__(self): def __str__(self):
return _("Template questions: {}").format(self.name) return _("Template questions: {}").format(self.name)
@@ -143,20 +147,20 @@ class TemplateQuestion(models.Model):
class SignupForm(models.Model): class SignupForm(models.Model):
"""Model for event signup form. Stores questions in JSON format.""" """Model for event signup form. Stores questions in JSON format."""
class Meta:
verbose_name = _("Signup form")
verbose_name_plural = _("Signup forms")
id = models.AutoField(primary_key=True) id = models.AutoField(primary_key=True)
title = models.CharField(max_length=255) title = models.CharField(max_length=255)
deleted = models.BooleanField(default=False)
visible = models.BooleanField(default=True)
start_time = models.DateTimeField(default=timezone.now) start_time = models.DateTimeField(default=timezone.now)
end_time = models.DateTimeField(default=timezone.now) end_time = models.DateTimeField(default=timezone.now)
questions = JSONField() questions = JSONField()
schema = JSONField() schema = JSONField()
visible = models.BooleanField(default=True)
quota = models.PositiveIntegerField(blank=True, null=True) quota = models.PositiveIntegerField(blank=True, null=True)
email_content = models.TextField(blank=True) email_content = models.TextField(blank=True)
deleted = models.BooleanField(default=False)
class Meta:
verbose_name = _("Signup form")
verbose_name_plural = _("Signup forms")
def __str__(self): def __str__(self):
delete_str = _("Deleted: ") if self.deleted else "" delete_str = _("Deleted: ") if self.deleted else ""
@@ -177,9 +181,12 @@ class Signup(models.Model):
Actual signup into any SignupForm. Deletes are soft. Actual signup into any SignupForm. Deletes are soft.
""" """
class Meta:
verbose_name = _("Sign-up")
verbose_name_plural = _("Sign-ups")
id = models.AutoField(primary_key=True) id = models.AutoField(primary_key=True)
signupForm = models.ForeignKey("SignupForm", on_delete=models.CASCADE) signupForm = models.ForeignKey("SignupForm", on_delete=models.CASCADE)
deleted = models.BooleanField(default=False)
time = models.DateTimeField(default=timezone.now) time = models.DateTimeField(default=timezone.now)
answer = JSONField() answer = JSONField()
# Answer we use in signupForm signups field. Frontend uses first questions answer as this value. # Answer we use in signupForm signups field. Frontend uses first questions answer as this value.
@@ -188,11 +195,9 @@ class Signup(models.Model):
email = models.EmailField(blank=True, null=True) email = models.EmailField(blank=True, null=True)
# Random unique identifier. Used for signup editing by the user. # Random unique identifier. Used for signup editing by the user.
uuid = models.UUIDField(default=uuid4, editable=False) uuid = models.UUIDField(default=uuid4, editable=False)
signupForm = models.ForeignKey("SignupForm", on_delete=models.CASCADE) # Random unique identifier generated by browser upon opening signup form. Used to prevent duplicate signups.
submit_id = models.UUIDField(null=True, editable=False, unique=True)
class Meta: deleted = models.BooleanField(default=False)
verbose_name = _("Sign-up")
verbose_name_plural = _("Sign-ups")
def __str__(self): def __str__(self):
delete_str = _("Deleted: ") if self.deleted else "" delete_str = _("Deleted: ") if self.deleted else ""
@@ -219,6 +224,49 @@ def email_on_signup(sender, instance, created, **kwargs):
) )
class JobAd(models.Model):
"""Job advertisements shown on Corporate relations page"""
class Meta:
verbose_name = _("JobAd")
verbose_name_plural = _("JobAds")
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=255)
description = models.CharField(max_length=255)
content = models.TextField()
visible = models.BooleanField(default=True)
created_at = models.DateTimeField(default=timezone.now)
autohide_at = models.DateTimeField(default=month_from_now)
autohide_enabled = models.BooleanField(default=False)
deleted = models.BooleanField(default=False)
def __str__(self):
delete_str = _("Deleted: ") if self.deleted else ""
return f"{delete_str}{self.title}"
__previousVisible = False
def __init__(self, *args, **kwargs):
super(JobAd, self).__init__(*args, **kwargs)
self.__previousVisible = self.visible
def save(self, force_insert=False, force_update=False, *args, **kwargs):
created = self.pk is None
super(JobAd, self).save(force_insert, force_update, *args, **kwargs)
if self.visible and (created or not self.__previousVisible):
self.refresh_from_db() # Fetch so we can use primary key
url = f"https://{FRONTEND_URL}/jobads/{self.pk}"
processHooks(
message=generateMessage(
"Uusi työpaikkailmoitus", self.title, self.description, url
),
eventType="jobad",
)
self.__previousVisible = self.visible
def generateMessage(heading: str, title: str, description: str, url: str): def generateMessage(heading: str, title: str, description: str, url: str):
return render_to_string( return render_to_string(
"webapp/tg_message.tpl", "webapp/tg_message.tpl",
@@ -301,9 +349,8 @@ class TelegramHook(BaseWebhook):
auditlog.register(Tag) auditlog.register(Tag)
auditlog.register(Feed) auditlog.register(Feed)
auditlog.register(Event) auditlog.register(Event)
auditlog.register(JobAd)
auditlog.register(TemplateQuestion)
auditlog.register(SignupForm) auditlog.register(SignupForm)
auditlog.register(Signup) auditlog.register(Signup)
auditlog.register(JobAd)
auditlog.register(GenericWebhook) auditlog.register(GenericWebhook)
auditlog.register(TelegramHook) auditlog.register(TelegramHook)
+65 -72
View File
@@ -2,19 +2,12 @@ from rest_framework import serializers
from webapp.models import * from webapp.models import *
class SavedQuestionsSerializer(serializers.ModelSerializer):
questions = serializers.JSONField()
class Meta:
model = TemplateQuestion
fields = ("id", "name", "questions")
class SignupSerializer(serializers.ModelSerializer): class SignupSerializer(serializers.ModelSerializer):
signupForm_id = serializers.PrimaryKeyRelatedField( signupForm_id = serializers.PrimaryKeyRelatedField(
source="signupForm", queryset=SignupForm.objects.all() source="signupForm", queryset=SignupForm.objects.all()
) )
list_name = serializers.CharField(read_only=True) list_name = serializers.CharField(read_only=True)
submit_id = serializers.UUIDField(required=False)
def add_extra_fields(self, validated_data): def add_extra_fields(self, validated_data):
questions = validated_data["signupForm"].questions questions = validated_data["signupForm"].questions
@@ -42,7 +35,7 @@ class SignupSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Signup model = Signup
fields = ("id", "signupForm_id", "answer", "list_name") fields = ("id", "submit_id", "signupForm_id", "answer", "list_name")
extra_kwargs = { extra_kwargs = {
"url": { "url": {
"view_name": "signup-detail", "view_name": "signup-detail",
@@ -76,54 +69,11 @@ class SignupFormSerializer(serializers.ModelSerializer):
) )
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ("id", "slug", "name_fi", "name_en", "icon")
class FeedSerializer(serializers.ModelSerializer):
tagId = serializers.PrimaryKeyRelatedField(
queryset=Tag.objects.all(),
many=True,
write_only=True,
)
class Meta:
model = Feed
fields = (
"id",
"title_fi",
"title_en",
"description_fi",
"description_en",
"content_fi",
"content_en",
"image",
"tags",
"tagId",
"isPublished",
"publishAt",
"autoUnpublish",
"unpublishAt",
)
read_only_fields = ["tags"]
depth = 1
def create(self, validated_data):
tags_data = validated_data.pop("tagId")
feed = Feed.objects.create(**validated_data)
for tag in tags_data:
feed.tags.add(tag)
feed.save()
return feed
class EventSerializer(serializers.ModelSerializer): class EventSerializer(serializers.ModelSerializer):
tagId = serializers.PrimaryKeyRelatedField( signupForm = SignupFormSerializer(
queryset=Tag.objects.all(), source="filtered_signup_forms",
many=True, many=True,
write_only=True, read_only=True,
) )
signup_id = serializers.PrimaryKeyRelatedField( signup_id = serializers.PrimaryKeyRelatedField(
@@ -131,30 +81,26 @@ class EventSerializer(serializers.ModelSerializer):
many=True, many=True,
write_only=True, write_only=True,
) )
tag_id = serializers.PrimaryKeyRelatedField(
signupForm = SignupFormSerializer( queryset=Tag.objects.all(),
source="filtered_signup_forms",
many=True, many=True,
read_only=True, write_only=True,
) )
class Meta: class Meta:
model = Event model = Event
fields = ( fields = (
"id", "id",
"tag_id",
"tags",
"visible",
"image",
"title_fi", "title_fi",
"title_en", "title_en",
"description_fi", "description_fi",
"description_en", "description_en",
"content_fi", "content_fi",
"content_en", "content_en",
"image",
"tags",
"tagId",
"isPublished",
"publishAt",
"autoUnpublish",
"unpublishAt",
"start_time", "start_time",
"end_time", "end_time",
"location_fi", "location_fi",
@@ -167,7 +113,7 @@ class EventSerializer(serializers.ModelSerializer):
def create(self, validated_data): def create(self, validated_data):
signupForms = validated_data.pop("signup_id", []) signupForms = validated_data.pop("signup_id", [])
tags = validated_data.pop("tagId") tags = validated_data.pop("tag_id")
event = Event.objects.create(**validated_data) event = Event.objects.create(**validated_data)
for form in signupForms: for form in signupForms:
event.signupForm.add(form) event.signupForm.add(form)
@@ -178,7 +124,7 @@ class EventSerializer(serializers.ModelSerializer):
def update(self, instance, validated_data): def update(self, instance, validated_data):
signupForms = validated_data.pop("signup_id", []) signupForms = validated_data.pop("signup_id", [])
tags = validated_data.pop("tagId") tags = validated_data.pop("tag_id")
instance.signupForm.clear() instance.signupForm.clear()
instance.tags.clear() instance.tags.clear()
for form in signupForms: for form in signupForms:
@@ -189,6 +135,54 @@ class EventSerializer(serializers.ModelSerializer):
return instance return instance
class SavedQuestionsSerializer(serializers.ModelSerializer):
questions = serializers.JSONField()
class Meta:
model = TemplateQuestion
fields = ("id", "name", "questions")
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ("id", "slug", "name_fi", "name_en", "icon")
class FeedSerializer(serializers.ModelSerializer):
tag_id = serializers.PrimaryKeyRelatedField(
many=True, source="tags", queryset=Tag.objects.all()
)
class Meta:
model = Feed
fields = (
"id",
"tags",
"tag_id",
"visible",
"image",
"title_fi",
"title_en",
"description_fi",
"description_en",
"content_fi",
"content_en",
"publish_time",
"autohide",
"autohide_enabled",
)
depth = 1
def create(self, validated_data):
tags_data = validated_data.pop("tags")
feed = Feed.objects.create(**validated_data)
for tag in tags_data:
feed.tags.add(tag)
feed.save()
return feed
class JobAdSerializer(serializers.ModelSerializer): class JobAdSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = JobAd model = JobAd
@@ -200,8 +194,7 @@ class JobAdSerializer(serializers.ModelSerializer):
"description_en", "description_en",
"content_fi", "content_fi",
"content_en", "content_en",
"isPublished", "visible",
"publishAt", "autohide_at",
"autoUnpublish", "autohide_enabled",
"unpublishAt",
) )
View File
+6 -6
View File
@@ -5,7 +5,7 @@ from webapp.utils import month_from_now
def createEventObject( def createEventObject(
name="Testitapahtuma1", name="Testitapahtuma1",
isPublished=True, visible=True,
start_time=timezone.now(), start_time=timezone.now(),
end_time=month_from_now(), end_time=month_from_now(),
tag_id=[], tag_id=[],
@@ -14,7 +14,7 @@ def createEventObject(
return Event.objects.create( return Event.objects.create(
title_fi=name, title_fi=name,
title_en=f"title_en {name}", title_en=f"title_en {name}",
isPublished=isPublished, visible=visible,
description_fi=f"desc_fi {name}", description_fi=f"desc_fi {name}",
description_en=f"desc_en {name}", description_en=f"desc_en {name}",
content_fi=f"content_fi {name}", content_fi=f"content_fi {name}",
@@ -27,15 +27,15 @@ def createEventObject(
def createEventJSON( def createEventJSON(
name="POST1", name="POST1",
isPublished=True, visible=True,
start_time=timezone.now(), start_time=timezone.now(),
end_time=month_from_now(), end_time=month_from_now(),
tagId=[], tag_id=[],
signup_id=[], signup_id=[],
): ):
return { return {
"tagId": tagId, "tag_id": tag_id,
"visible": isPublished, "visible": visible,
"title_fi": f"title_fi {name}", "title_fi": f"title_fi {name}",
"title_en": f"title_en {name}", "title_en": f"title_en {name}",
"description_fi": f"desc_fi {name}", "description_fi": f"desc_fi {name}",
+16 -11
View File
@@ -2,6 +2,7 @@ from django.contrib.auth.models import User, AnonymousUser
from django.utils import timezone from django.utils import timezone
from rest_framework import status from rest_framework import status
from rest_framework.test import APITestCase, APIRequestFactory from rest_framework.test import APITestCase, APIRequestFactory
import zoneinfo
from webapp.models import Event from webapp.models import Event
from webapp.serializers import EventSerializer from webapp.serializers import EventSerializer
@@ -14,28 +15,30 @@ URL = "/api/events/"
class EventTestCase(APITestCase): class EventTestCase(APITestCase):
def setUp(self): def setUp(self):
tz = zoneinfo.ZoneInfo(key="Europe/Helsinki")
# Visible and relevant # Visible and relevant
test1 = createEventObject( test1 = createEventObject(
"Testitapahtuma1", start_time=timezone.datetime(2019, 11, 9, 12, 0, 0) "Testitapahtuma1",
start_time=timezone.datetime(2019, 11, 9, 12, 0, 0, tzinfo=tz),
) )
# Invisible but relevant # Invisible but relevant
createEventObject( createEventObject(
"Testitapahtuma2", "Testitapahtuma2",
isPublished=False, visible=False,
start_time=timezone.datetime(2018, 11, 9, 12, 0, 0), start_time=timezone.datetime(2018, 11, 9, 12, 0, 0, tzinfo=tz),
) )
# Visible but unrelevant # Visible but unrelevant
test2 = createEventObject( test2 = createEventObject(
"Testitapahtuma3", "Testitapahtuma3",
isPublished=True, visible=True,
start_time=timezone.datetime(2018, 12, 9, 12, 0, 0), start_time=timezone.datetime(2018, 12, 9, 12, 0, 0, tzinfo=tz),
end_time=timezone.datetime(2018, 12, 9, 13, 0, 0), end_time=timezone.datetime(2018, 12, 9, 13, 0, 0, tzinfo=tz),
) )
# Visible and relevant # Visible and relevant
createEventObject( createEventObject(
"Testitapahtuma4", "Testitapahtuma4",
isPublished=True, visible=True,
start_time=timezone.datetime(2018, 12, 9, 12, 0, 0), start_time=timezone.datetime(2018, 12, 9, 12, 0, 0, tzinfo=tz),
) )
# Add some tags # Add some tags
tag1 = tagBuilder() tag1 = tagBuilder()
@@ -77,7 +80,9 @@ class EventTestCase(APITestCase):
self.assertEqual(response.data["results"], expected) self.assertEqual(response.data["results"], expected)
def test_get_events_since(self): def test_get_events_since(self):
response = self.client.get(f"{URL}?since=2018-01-01", format="json") response = self.client.get(
f"{URL}?since=2018-01-01%2000:00:00%2B0200", format="json"
)
self.assertTrue(response.status_code, status.HTTP_200_OK) self.assertTrue(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data["results"]), 3) self.assertEqual(len(response.data["results"]), 3)
@@ -122,7 +127,7 @@ class EventTestCase(APITestCase):
self.client.force_authenticate(user=self.authClient) self.client.force_authenticate(user=self.authClient)
response = self.client.post( response = self.client.post(
URL, URL,
createEventJSON(tagId=[self.testTagId], signup_id=[self.signupFormId]), createEventJSON(tag_id=[self.testTagId], signup_id=[self.signupFormId]),
format="json", format="json",
) )
@@ -132,7 +137,7 @@ class EventTestCase(APITestCase):
def test_post_event_unauth(self): def test_post_event_unauth(self):
response = self.client.post( response = self.client.post(
URL, URL,
createEventJSON(tagId=[self.testTagId], signup_id=[self.signupFormId]), createEventJSON(tag_id=[self.testTagId], signup_id=[self.signupFormId]),
format="json", format="json",
) )
+3 -3
View File
@@ -16,7 +16,7 @@ class FeedTestCase(APITestCase):
feed = Feed.objects.create( feed = Feed.objects.create(
title="TestFeed", title="TestFeed",
isPublished=True, visible=True,
description="diidadaapa", description="diidadaapa",
content="lorem ipsum", content="lorem ipsum",
) )
@@ -51,10 +51,10 @@ class FeedTestCase(APITestCase):
tag2_id = tagBuilder("test2").id tag2_id = tagBuilder("test2").id
data = { data = {
"tagId": [tag1_id, tag2_id], "tag_id": [tag1_id, tag2_id],
"title_fi": "testtitle", "title_fi": "testtitle",
"title_en": "testtitle", "title_en": "testtitle",
"isPublished": "True", "visible": "True",
"description_fi": "liirumlaarum", "description_fi": "liirumlaarum",
"description_en": "liirumlaarum", "description_en": "liirumlaarum",
"content_fi": "lorem ipsum", "content_fi": "lorem ipsum",
+3 -3
View File
@@ -13,7 +13,7 @@ class JobAdTestCase(APITestCase):
self.prefilled_jobad = JobAd.objects.create( self.prefilled_jobad = JobAd.objects.create(
title_fi="ABB Test", title_fi="ABB Test",
title_en="ABB Test", title_en="ABB Test",
isPublished=True, visible=True,
description_fi="desc", description_fi="desc",
description_en="desc", description_en="desc",
content_fi="lorem", content_fi="lorem",
@@ -35,12 +35,12 @@ class JobAdTestCase(APITestCase):
data = { data = {
"title_fi": "testtitle", "title_fi": "testtitle",
"title_en": "testtitle", "title_en": "testtitle",
"isPublished": "True", "visible": "True",
"description_fi": "liirumlaarum", "description_fi": "liirumlaarum",
"description_en": "liirumlaarum", "description_en": "liirumlaarum",
"content_fi": "lorem ipsum", "content_fi": "lorem ipsum",
"content_en": "lorem ipsum", "content_en": "lorem ipsum",
"autoUnpublish": "True", "autohide_enabled": "True",
} }
# Try post without authentication # Try post without authentication
+19 -19
View File
@@ -4,18 +4,9 @@ from modeltranslation.translator import register, TranslationOptions
from webapp.models import * from webapp.models import *
@register(Tag)
class TagTranslationOptions(TranslationOptions):
fields = ("name",)
@register(BaseFeed) @register(BaseFeed)
class BaseFeedTranslationOptions(TranslationOptions): class BaseFeedTranslationOptions(TranslationOptions):
fields = ( fields = ("title", "description", "content")
"title",
"description",
"content",
)
@register(Feed) @register(Feed)
@@ -23,18 +14,18 @@ class FeedTranslationOptions(TranslationOptions):
fields = () fields = ()
@register(Tag)
class TagTranslationOptions(TranslationOptions):
fields = ("name",)
@register(Event) @register(Event)
class EventTranslationOptions(TranslationOptions): class EventTranslationOptions(TranslationOptions):
fields = ("location",) fields = ("location",)
@register(JobAd) @register(Signup)
class JobAdTranslationOptions(TranslationOptions): class SignupTranslationOptions(TranslationOptions):
fields = ()
@register(TemplateQuestion)
class TemplateQuestionTranslationOptions(TranslationOptions):
fields = () fields = ()
@@ -43,11 +34,20 @@ class SignupFormTranslationOptions(TranslationOptions):
fields = ("title",) fields = ("title",)
@register(Signup) @register(TemplateQuestion)
class SignupTranslationOptions(TranslationOptions): class TemplateQuestionTranslationOptions(TranslationOptions):
fields = () fields = ()
@register(JobAd)
class JobAdTranslationOptions(TranslationOptions):
fields = (
"title",
"description",
"content",
)
@register(BaseWebhook) @register(BaseWebhook)
class BaseWebhookOptions(TranslationOptions): class BaseWebhookOptions(TranslationOptions):
fields = () fields = ()

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