Compare commits
741 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8164094b44 | |||
| fd12f35e2e | |||
| f69615437a | |||
| 533f367b4e | |||
| 93202d8a11 | |||
| e1e1adc397 | |||
| 0df2d4ab46 | |||
| 4e0a93631d | |||
| be9e308587 | |||
| 738051355b | |||
| 8550a9a02b | |||
| 1cf67c7686 | |||
| 703bb91bfd | |||
| 2ff0cab544 | |||
| 6b4a00ebd8 | |||
| 677c1400fa | |||
| a2e08d0ea6 | |||
| 901f2bed96 | |||
| 6004156b6f | |||
| e00323bffe | |||
| 6ef0dbf91b | |||
| 2169bad90d | |||
| 63a4781574 | |||
| e735ebe64a | |||
| f9db8476a1 | |||
| 2a24544056 | |||
| fb7bee5480 | |||
| 8355d10635 | |||
| 21892e277e | |||
| 81e1f994eb | |||
| c79d824a8a | |||
| fc6e02b71b | |||
| 1e14f98f9d | |||
| 8815ccf667 | |||
| b694370572 | |||
| f51611bbee | |||
| fda9acfb2f | |||
| 30c6e4809b | |||
| 4373f37dfe | |||
| 39754a5e63 | |||
| 0be3ee69be | |||
| c6f0f4615b | |||
| 3ac5400b79 | |||
| 429d3a0602 | |||
| 1b086843dc | |||
| b9280ea026 | |||
| b36022e546 | |||
| 9bb57840a7 | |||
| 36bd74c6cc | |||
| a2615ae27d | |||
| f7de9e32d3 | |||
| 57d8c4321f | |||
| 632eedea9c | |||
| 1405d89d9a | |||
| da1ae8d721 | |||
| 52a83b9336 | |||
| f67ce55d60 | |||
| d8fdc2cc74 | |||
| 5e1390ab6b | |||
| eb2ae3368a | |||
| 99348dc297 | |||
| 715b309c89 | |||
| f7f63b8670 | |||
| 70676d5203 | |||
| 037e4ae6e8 | |||
| 53742e5caa | |||
| 8d6f13b61d | |||
| 03982ee620 | |||
| 2affae7bfd | |||
| a8923b63d6 | |||
| 19975877cb | |||
| 2e0fad4bb2 | |||
| f0179c1840 | |||
| 37a9750d4d | |||
| 5575186570 | |||
| 9e179d5e06 | |||
| f7659e1e1b | |||
| ea9a732803 | |||
| a4ae136e1a | |||
| 0026b788b2 | |||
| 570fff1d7d | |||
| da3a484f6c | |||
| 9d116528b9 | |||
| a310d51f5e | |||
| 6732e30213 | |||
| afbdca1501 | |||
| 4e59eee200 | |||
| 03e4ccdf5d | |||
| bb0b2a2628 | |||
| 16454ebdf6 | |||
| 32d636d3ee | |||
| 2e2464fb5f | |||
| c91b99cdb1 | |||
| caf2113e49 | |||
| c1a1f6e534 | |||
| f79d1467f7 | |||
| 40cf9121b6 | |||
| ca73eba609 | |||
| fe46d57108 | |||
| 70e1835a4f | |||
| b7f17671d9 | |||
| 34659403a8 | |||
| 4fbf5fe0a4 | |||
| c6be0e6562 | |||
| 3623c7e9f4 | |||
| 298db5b78e | |||
| 1ca6de3090 | |||
| 07d0f2aa47 | |||
| 93e122b8a8 | |||
| 9678b663a0 | |||
| 992a2ec8e0 | |||
| b41bd41a54 | |||
| 30f59c36fb | |||
| f51d71e045 | |||
| 9c66238b82 | |||
| 2f0143a9ae | |||
| 45ff2c3757 | |||
| 321d45b628 | |||
| 2628d753f5 | |||
| a603e2dff8 | |||
| 96e05d908d | |||
| a5bf5668eb | |||
| 1ec5082faf | |||
| 6da5b97e19 | |||
| 7fd30e3eba | |||
| 3e9084ca1d | |||
| a28f82d31e | |||
| 04ecb8fc7e | |||
| 3e707e58a5 | |||
| 70d7f55996 | |||
| e408809e58 | |||
| 33fd4012f1 | |||
| 228938b695 | |||
| 72e91e3d62 | |||
| 3f6a719e9d | |||
| 490b99a848 | |||
| 0a899f5600 | |||
| 7825cc7293 | |||
| 8bb6e9e9a7 | |||
| 53c3acd39f | |||
| c45bcc5442 | |||
| dd0254a08e | |||
| 9b53fb4bc0 | |||
| 2383e2089d | |||
| e17c3ad92c | |||
| 362d981532 | |||
| e12be3c2f6 | |||
| 3edae7f967 | |||
| 4d159b2793 | |||
| cb3b831f7a | |||
| acba330694 | |||
| eb22368055 | |||
| 5f467323b5 | |||
| fac2f9b367 | |||
| 7319c32d73 | |||
| b3a484ce55 | |||
| 337b774074 | |||
| e70e598c57 | |||
| 5eef2f685c | |||
| d1953ef24c | |||
| a9c122c0d4 | |||
| ec4317d9e7 | |||
| d4a219290b | |||
| cff84816fc | |||
| 7881a24eb1 | |||
| e74580fdde | |||
| a0765ca18b | |||
| 913eb1cedf | |||
| 7035ebccca | |||
| 2031146fc7 | |||
| 35f30300b3 | |||
| 342f2862a5 | |||
| 3536ca5922 | |||
| 50ab7bc1f9 | |||
| 102d8f82d6 | |||
| f302c0a17d | |||
| a2b7086e9a | |||
| 704652c643 | |||
| 8741f6b113 | |||
| 9ffb79aa52 | |||
| a2551cc110 | |||
| 8c90471eb1 | |||
| 8d7bd7067e | |||
| db3e3ae291 | |||
| 1afd476c18 | |||
| 322cc9d6fb | |||
| 5f9a7a6994 | |||
| 3809ba9726 | |||
| 79cc0bcd55 | |||
| 7cb03d40d4 | |||
| 34faf53347 | |||
| 8d2a26a9d1 | |||
| a05edb73d3 | |||
| 2c69e1b945 | |||
| 9cae6ea890 | |||
| 49a3b1449e | |||
| 02634c8e02 | |||
| fc73424665 | |||
| 11efcdd579 | |||
| a0f062c697 | |||
| 1dc5d45e96 | |||
| 7382c4e4bf | |||
| 867996ae27 | |||
| 798c860091 | |||
| 86a65e4680 | |||
| 4a530826a8 | |||
| 6f316401f7 | |||
| 8a8820be2f | |||
| 51278fd8bc | |||
| 2d86d548a3 | |||
| b610a8af6e | |||
| 6380d39afb | |||
| 6022b11dc1 | |||
| 9420b1fd05 | |||
| fcd23c07ab | |||
| c92193057c | |||
| a845be5394 | |||
| c4a0f5a0ea | |||
| 14735d8898 | |||
| d8250c691c | |||
| 87d14240cf | |||
| 50d4006b96 | |||
| d9b006904a | |||
| 3aa225f3cd | |||
| a4e1aa5032 | |||
| 72ea31a887 | |||
| 912ce44513 | |||
| 6d093af511 | |||
| be7a5fe2e2 | |||
| 40f6558a6e | |||
| 808c27e104 | |||
| b8342ba66a | |||
| 9d05cd8290 | |||
| 4860c0a0d9 | |||
| 40b824355b | |||
| 9d4c041153 | |||
| ce72ff7801 | |||
| 5f69f34bf3 | |||
| fb3328cb23 | |||
| c0db047cd9 | |||
| e2c32e81a7 | |||
| dac0fa1953 | |||
| 63672328f0 | |||
| a927ab8a13 | |||
| c6374d88b6 | |||
| f72017df01 | |||
| 26635bbbcc | |||
| 86973562ff | |||
| 0c1bdba358 | |||
| 8481e963a3 | |||
| 39ba51f11a | |||
| d889df951c | |||
| 19f8ff006c | |||
| ed77526f5c | |||
| 6f6dc09f01 | |||
| 5049ef415d | |||
| 441227bc15 | |||
| 78575075e5 | |||
| 4029031bf0 | |||
| f1ea08bc30 | |||
| e68f04117d | |||
| d07914bdb5 | |||
| 38eb4cd9cd | |||
| ab104d2acd | |||
| 0376acdc9e | |||
| 6a65ca32d7 | |||
| abbf23456b | |||
| 6a156dd9db | |||
| 99739cd035 | |||
| 9fb9bd447a | |||
| ba69f54465 | |||
| 9a450ea44a | |||
| 242a45a92e | |||
| 8454e67a92 | |||
| ad472ca8ff | |||
| 928a98904b | |||
| 2882c1a4c8 | |||
| 1e0c050f99 | |||
| 744874a54c | |||
| 4dfa033f0b | |||
| d56ab02c27 | |||
| 01c5a0ed53 | |||
| 06fcfcf3a1 | |||
| 6567cd87d5 | |||
| 9efe0d7489 | |||
| 33c7c20140 | |||
| d7b6ef3c72 | |||
| 0a31492733 | |||
| c96ff2f112 | |||
| 2b0d1c6ca7 | |||
| 90342011f5 | |||
| 27f9c79cf3 | |||
| de1ba7c9f2 | |||
| c2fb77816a | |||
| 5a77b1546d | |||
| 4be0b75a1c | |||
| 09aa42cfb8 | |||
| c5008e3656 | |||
| e5224fe1e2 | |||
| b834567309 | |||
| b1921d4926 | |||
| 00c6920c8a | |||
| 1fe323cbdd | |||
| 3a58ff0ef1 | |||
| d06c24bde0 | |||
| 2359743b20 | |||
| 437936ad64 | |||
| 27dd56b6fb | |||
| ff5a92897c | |||
| e9c38d56d8 | |||
| 57bc12b621 | |||
| 1ff188eddd | |||
| 81971b6da4 | |||
| b443b39457 | |||
| a68fa9a6d6 | |||
| 22615da3b4 | |||
| d09cbf3b2c | |||
| 66fcfab4da | |||
| a7d0f49190 | |||
| 519571f84c | |||
| 30847e8bc2 | |||
| df69a64ffc | |||
| 034e04a788 | |||
| 157f1c487c | |||
| 1e5a691d73 | |||
| 43fe25ba64 | |||
| 851269b13f | |||
| 0e5e18b035 | |||
| f543e83e80 | |||
| 0b72c20e32 | |||
| 04e6eb3ec2 | |||
| 4e1efff7f2 | |||
| d45f7195f5 | |||
| dc6347a6eb | |||
| 1304cffa2a | |||
| d416fda39e | |||
| 656b2d1d95 | |||
| 9b9406ab4b | |||
| e4fbb58026 | |||
| 3630b360d7 | |||
| c6a3445774 | |||
| 9b7e9a6d92 | |||
| 3de975203e | |||
| 38669947cd | |||
| e03b6e6606 | |||
| 8491842a04 | |||
| eb80250335 | |||
| dec348b032 | |||
| 56dfd57698 | |||
| 5071bd6172 | |||
| 8632aa01da | |||
| f98971270a | |||
| e2d55ffce2 | |||
| 37cd2eda4c | |||
| aa0ae01227 | |||
| 54ded7d3f9 | |||
| ef38d8be46 | |||
| 673bbc09eb | |||
| 9fa62e9a2f | |||
| 32e0704f53 | |||
| 39ab86fe3e | |||
| f9e7c4a904 | |||
| 0adca6bd2e | |||
| 5c4e4c7f5a | |||
| 76e1c71cd1 | |||
| 67b31060b2 | |||
| 110d426efd | |||
| 62bb317c16 | |||
| a379390ca1 | |||
| 75ece86fb0 | |||
| 26af46fa12 | |||
| 027b9c370c | |||
| 59c9fec51c | |||
| 98ea2d8a47 | |||
| 0b9aed128a | |||
| 4ebba7f74a | |||
| 11c36f2111 | |||
| ef78ef9dcd | |||
| 34984d4a5e | |||
| 319324503f | |||
| 683f50b199 | |||
| 2d6c474baa | |||
| b302681bbf | |||
| 90d8ee2ea9 | |||
| f505fae3e6 | |||
| 2433c7828d | |||
| 0edde936cc | |||
| 2e11621e5b | |||
| 5f706d9236 | |||
| d3350c06e0 | |||
| 8b1f668d38 | |||
| 648feaa762 | |||
| 6d4662a1cb | |||
| 523adcd256 | |||
| 5f0ab2ddbf | |||
| 6e3b3bd52f | |||
| 027bf5e7bd | |||
| 6dcbc1c652 | |||
| 80e00e2b05 | |||
| be22fea3f2 | |||
| 80298d174f | |||
| 5e434408b0 | |||
| b790906b23 | |||
| e7164bb613 | |||
| a2a2b9830e | |||
| eb0bfdbd21 | |||
| 41762e920f | |||
| d9116e51ca | |||
| 3188a8e520 | |||
| a9c1d06ece | |||
| f390e1fb1f | |||
| c32292c7d9 | |||
| f2ffcaa5a9 | |||
| 4e35a73a4b | |||
| d0216bb85c | |||
| 6955659acb | |||
| 62263848f1 | |||
| dbc7811651 | |||
| 242199399b | |||
| 66d9ee0a75 | |||
| c2222d4426 | |||
| 368b28d241 | |||
| c5df450a96 | |||
| c1e0a32a08 | |||
| 373db50995 | |||
| f422e31a42 | |||
| f7cf54cab2 | |||
| d17e6710e6 | |||
| f64cec7d2e | |||
| ff87767aab | |||
| 08ec9bb9cc | |||
| eb63c9fc1c | |||
| eec157d7f3 | |||
| 1ce3c28e94 | |||
| 52a646fe04 | |||
| 8c4f6e886c | |||
| a9d6bf50d6 | |||
| 8f0c9feecf | |||
| 5fba7d6249 | |||
| 3d755bca8c | |||
| ff04e645e4 | |||
| 2c72e01353 | |||
| 3836e00174 | |||
| b33f29d620 | |||
| 8afeec21cc | |||
| 1778a0509c | |||
| 86ae2197f3 | |||
| 4859daf0b3 | |||
| ff9cad1f47 | |||
| 01161db33b | |||
| a7728e50dd | |||
| edb6d73c06 | |||
| 0f50daf289 | |||
| f4b55b7863 | |||
| 00de36ca44 | |||
| 59f47dec26 | |||
| 8c4b997f0f | |||
| b2e2ff6699 | |||
| ff98f700f9 | |||
| fe48a2a336 | |||
| 80c8d9bc90 | |||
| 31ebace4cd | |||
| b3a3a9815f | |||
| 81a16680e4 | |||
| 04e91a0e8d | |||
| 72b2a18c63 | |||
| 029089b32d | |||
| 3ea513785a | |||
| 8dc576983c | |||
| a7ada33ff9 | |||
| 59a59685d1 | |||
| 2f0024630d | |||
| e9706d0e4f | |||
| baa329ad5d | |||
| 389408a59e | |||
| 2f4009d002 | |||
| dab6f98ec2 | |||
| b23e439eb5 | |||
| fdea5858ba | |||
| 96fc8a5569 | |||
| c408a6532e | |||
| 09e973fab1 | |||
| 971247e67c | |||
| 59dcb382ce | |||
| 86af724ccc | |||
| e5cc62bbbf | |||
| 4b63de6fc4 | |||
| d0d63a6e29 | |||
| d7f57c5b3a | |||
| 777cca4917 | |||
| 6c4a26eb44 | |||
| e05fcb423c | |||
| 7457b8a24d | |||
| fb9dbe2cd2 | |||
| 8a3fdc40eb | |||
| 192be6e28b | |||
| 6ba91f1fab | |||
| b346c2122a | |||
| 4725432c32 | |||
| 4e5b6c565a | |||
| 4e216891e6 | |||
| 562738f945 | |||
| 7ffddf41f8 | |||
| c9c814e405 | |||
| 88f37928ac | |||
| fcd7ae7c04 | |||
| 0f72a7ea21 | |||
| 995d42640f | |||
| 135c0309b5 | |||
| 13583bd6ab | |||
| 522c804369 | |||
| bd4551a222 | |||
| 046ae89d29 | |||
| 7fee47f150 | |||
| 9ffec51537 | |||
| c07ec5855a | |||
| c445a4a66c | |||
| b0dd95fa86 | |||
| 96dd77e455 | |||
| 9516dbbbea | |||
| 84f1f9239b | |||
| 5b2546217c | |||
| a24a7f02b9 | |||
| 2646914b8a | |||
| d3c50dc608 | |||
| 17c00e519b | |||
| 34a4f027f5 | |||
| 389771d106 | |||
| 66ed4c0713 | |||
| ed4e226186 | |||
| 1038e9c67e | |||
| bc7a8ebd17 | |||
| 6a3398e8de | |||
| cfdac40d3b | |||
| 8a87288d25 | |||
| 1d4a509c7e | |||
| ec4be22552 | |||
| 29d2f6b90d | |||
| 06d5e3c6f4 | |||
| 5d6cf1fb80 | |||
| 95e72b7ab2 | |||
| 89cd91dbad | |||
| d17d1fb1b7 | |||
| d0d822b8f4 | |||
| 44c091f2d5 | |||
| f6abe6f999 | |||
| 8a5bbaa229 | |||
| c1b3bedf8d | |||
| 5750d62fe0 | |||
| 3c16029e88 | |||
| 71e683fbe3 | |||
| 41d6f17716 | |||
| e214144ec3 | |||
| 2649afdd4b | |||
| 0faf19e319 | |||
| 8ee514326a | |||
| 10552c5f2a | |||
| 48e34ece4f | |||
| 4181b246a1 | |||
| c39979e253 | |||
| 807c599b85 | |||
| d2af34bf0c | |||
| 9e186c32ba | |||
| ddf4ad7b8d | |||
| e3d0709b31 | |||
| 3169191a0d | |||
| bd8fc6b404 | |||
| 8500ed8fbd | |||
| cb86c9a2ae | |||
| bb0f4244c7 | |||
| cd2fe4e8f2 | |||
| bf3f1f783c | |||
| 37f509ff76 | |||
| b58dbde83c | |||
| 0da0a02bac | |||
| fef74dd995 | |||
| 2356365197 | |||
| 5a29c8c49d | |||
| e365c394d8 | |||
| 22371b8f15 | |||
| 32feb46f88 | |||
| 6c77fe4da6 | |||
| 5dc7442d03 | |||
| 9f664737b6 | |||
| 1c3b4367a2 | |||
| 9a9757c914 | |||
| 2bf19d357e | |||
| 7e31f72301 | |||
| 68bb003411 | |||
| 7133f36e5d | |||
| c9962c4d8e | |||
| 5832bd1765 | |||
| 4046d2c753 | |||
| 93298618ae | |||
| 709a637ae2 | |||
| a88bb30b62 | |||
| 38527207ac | |||
| 6f90fcc016 | |||
| 1e633ded31 | |||
| 4b3b8be4e4 | |||
| effd58a424 | |||
| 850587006c | |||
| 423b574cca | |||
| 7bb4a6cfc1 | |||
| 94f392ab87 | |||
| ccef40e63f | |||
| 1c51b666ab | |||
| b5462d5757 | |||
| 587632d7a3 | |||
| 11c92cdd5f | |||
| e4577e13e0 | |||
| 6365e0c63e | |||
| 4735541344 | |||
| 8e40434867 | |||
| 8194ab43cd | |||
| a708a6377d | |||
| 3fc810697a | |||
| 3305704926 | |||
| 206ec80a9a | |||
| dce19b8d38 | |||
| 8a8751ce66 | |||
| 0f0389b8ff | |||
| 061c9c8483 | |||
| 005d34daf5 | |||
| 95e028d546 | |||
| 95c29d8528 | |||
| bda342b05b | |||
| 889ad4f980 | |||
| a5bca8eab1 | |||
| 2dfb53bd5e | |||
| 474df33a99 | |||
| a2906966ba | |||
| a81096fa13 | |||
| b56ff6c5cd | |||
| 168de8088b | |||
| e7862df57c | |||
| ec829a5683 | |||
| bb3476a8ad | |||
| bab5c88061 | |||
| 7431aadfba | |||
| 1ecf3c368b | |||
| d3e6013840 | |||
| e1220d17bb | |||
| 55fdf8f60d | |||
| 65aac3daf1 | |||
| 1f8e9e582a | |||
| 97e6ce3d8d | |||
| 316ab679f8 | |||
| bf5620c3b6 | |||
| 642aa9847f | |||
| 90f430c95c | |||
| bf4c5bf3d9 | |||
| b7beb72409 | |||
| 0617dd6a93 | |||
| d99f9c53f7 | |||
| 3cfa0df43b | |||
| dc2982965e | |||
| 3c58d764bd | |||
| 4d2584dee4 | |||
| 3294d6ffa0 | |||
| 8398928a97 | |||
| 09a2e09c1b | |||
| d0f5bc223b | |||
| 7a58fb0af5 | |||
| 395c597a7f | |||
| e43734e32e | |||
| ef6b8ed5a5 | |||
| 2ae9ee7352 | |||
| 5967d0f7b3 | |||
| 8f9c504bec | |||
| 32771493f3 | |||
| c654c41380 | |||
| e4cffd5667 | |||
| fa0954e8ca | |||
| aeda06374d | |||
| ac64a1766f | |||
| 884c2552a8 | |||
| f9082237e6 | |||
| a72b313b83 | |||
| 94f8a92e65 | |||
| f80cbd7457 | |||
| 009495b11b | |||
| f9bb4bba25 | |||
| fe71f24ced | |||
| af2719082e | |||
| 86e2827f3b | |||
| 5479f0e1a7 | |||
| c219f32266 | |||
| e111d1884c | |||
| 7554750883 | |||
| e75f0cfc67 | |||
| 7d3208651a | |||
| e949e93799 | |||
| f544cf6183 | |||
| 197c7b0a8b | |||
| abb3f6659d | |||
| 8141ccfa13 | |||
| f395e8de06 | |||
| 7614c127ba | |||
| b2cc29f6af | |||
| 7efb8d3e7b | |||
| 48fa13e37b | |||
| d5ead73869 | |||
| a81f305e54 | |||
| dcef7aa49a | |||
| e0f571a201 | |||
| e5e9774681 | |||
| 6f18fa0aea | |||
| 9a918da2f0 | |||
| 17d1d4fea2 | |||
| 85fcf6feb9 | |||
| 89665d2b48 | |||
| c3478ff47c | |||
| b77dd3f50e | |||
| 7009f38652 | |||
| 4ff426327b | |||
| d7d0b377f2 | |||
| 4f499af4cf | |||
| d9ed6e7b17 | |||
| efbfe64d1e | |||
| cc9794d6a8 | |||
| 19ff70d0bd | |||
| d82e3ec4d9 | |||
| d0825e96bc | |||
| a981f6a31f | |||
| 524c6bbb9e | |||
| ac4902f867 | |||
| 416be8e2cd | |||
| 4cee5582da | |||
| 160f5adc59 | |||
| b8267e411c | |||
| ab2fcf04eb | |||
| 4a79ed341e | |||
| 7b8c5f3d8f | |||
| 170635a8db | |||
| a86f71b422 | |||
| 2585c4885b | |||
| 1cba13f4ff | |||
| 63e669ff0c | |||
| 2d95dbb0f1 | |||
| 8f50a6e0bf |
@@ -1,3 +1,7 @@
|
||||
[report]
|
||||
show_missing = True
|
||||
[run]
|
||||
omit =
|
||||
*/migrations/*
|
||||
*/admin.py
|
||||
*/translation.py
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "Deploying to development."
|
||||
set -e
|
||||
set -x
|
||||
|
||||
pushd deployment
|
||||
|
||||
docker-compose down
|
||||
docker pull "$1"
|
||||
docker-compose up -d
|
||||
|
||||
popd
|
||||
|
||||
set +x
|
||||
set +e
|
||||
@@ -0,0 +1,32 @@
|
||||
.DS_Store
|
||||
.dockerignore
|
||||
.git/
|
||||
.husky/
|
||||
.venv/
|
||||
.vscode/
|
||||
collected_static/
|
||||
logs/
|
||||
!logs/README
|
||||
media/
|
||||
!media/REMOVE_ME
|
||||
misc/
|
||||
node_modules/
|
||||
scripts/
|
||||
.coverage
|
||||
.coveragerc
|
||||
.env*
|
||||
.eslintignore
|
||||
.eslintrc.json
|
||||
.gitignore
|
||||
.gitlab-ci.yml
|
||||
.python-version
|
||||
docker-compose.yml
|
||||
!manage.py
|
||||
package*.json
|
||||
!poetry.lock
|
||||
!production_entrypoint.sh
|
||||
pycodestyle.cfg
|
||||
!pyproject.toml
|
||||
pyright.json
|
||||
README.md
|
||||
stack-compose*.yml
|
||||
@@ -0,0 +1,13 @@
|
||||
DEPLOY_ENV=local
|
||||
SENTRY_DSN=
|
||||
HOST=localhost
|
||||
DEBUG=True
|
||||
SECRET_KEY=7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp(
|
||||
DB_NAME=postgres
|
||||
DB_USER=postgres
|
||||
DB_PASSWD=postgres
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
EMAIL_API_KEY=
|
||||
GROUP_KEY=
|
||||
GOOGLE_CREDS='{}'
|
||||
@@ -0,0 +1,13 @@
|
||||
DEPLOY_ENV=local
|
||||
#SENTRY_DSN=
|
||||
HOST=localhost
|
||||
DEBUG=True
|
||||
SECRET_KEY=7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp(
|
||||
DB_NAME=postgres
|
||||
DB_USER=postgres
|
||||
DB_PASSWD=postgres
|
||||
DB_HOST=db
|
||||
DB_PORT=5432
|
||||
EMAIL_API_KEY=
|
||||
GROUP_KEY=
|
||||
GOOGLE_CREDS='{}'
|
||||
@@ -3,3 +3,4 @@ infoscreen/static/js/lib
|
||||
webapp/static/js/lib
|
||||
static/js/lib
|
||||
collected_static
|
||||
venv
|
||||
|
||||
@@ -4,11 +4,10 @@
|
||||
"jquery": true
|
||||
},
|
||||
"globals": {
|
||||
"angular": 1,
|
||||
"noty": 1,
|
||||
"app": 1,
|
||||
"_": 1,
|
||||
"moment": 1
|
||||
"angular": true,
|
||||
"noty": true,
|
||||
"_": true,
|
||||
"moment": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"rules": {
|
||||
|
||||
@@ -1,23 +1,14 @@
|
||||
*.swp
|
||||
sikweb/settings.py
|
||||
*~
|
||||
*.pyc
|
||||
*.sqlite3
|
||||
uwsgi.ini
|
||||
uwsgi.log
|
||||
infoscreen/static/js/hsl.json
|
||||
members/logs/*
|
||||
.idea/
|
||||
logs/
|
||||
/media/
|
||||
node_modules/
|
||||
/.coverage
|
||||
db.sqlite3
|
||||
requirements_henu.txt
|
||||
/collected_static/
|
||||
mydatabase
|
||||
settings.json
|
||||
.vscode/
|
||||
.DS_Store
|
||||
.env
|
||||
*.pyc
|
||||
/collected_static/
|
||||
/media/
|
||||
logs/
|
||||
members/logs/*
|
||||
node_modules/
|
||||
.coverage
|
||||
.vscode/
|
||||
.idea/
|
||||
*.code-workspace
|
||||
sik_test
|
||||
venv/
|
||||
.venv/
|
||||
@@ -1,101 +1,191 @@
|
||||
stages:
|
||||
- test
|
||||
- setup
|
||||
- audit
|
||||
- lint
|
||||
- test
|
||||
- publish
|
||||
- deploy
|
||||
- cleanup
|
||||
|
||||
install:
|
||||
image: node:14
|
||||
stage: setup
|
||||
only:
|
||||
- pushes
|
||||
script:
|
||||
- npm ci
|
||||
artifacts:
|
||||
paths:
|
||||
- node_modules
|
||||
expire_in: 1 week
|
||||
|
||||
audit:
|
||||
image: python:3.9
|
||||
stage: audit
|
||||
only:
|
||||
- pushes
|
||||
- develop
|
||||
except:
|
||||
- master
|
||||
needs: []
|
||||
before_script:
|
||||
- pip install poetry==1.3.1
|
||||
- poetry config virtualenvs.create false
|
||||
- poetry install --no-interaction --no-ansi
|
||||
script:
|
||||
- safety check
|
||||
|
||||
test:
|
||||
image: python:3.5
|
||||
image: python:3.9
|
||||
stage: test
|
||||
only:
|
||||
- pushes
|
||||
needs: []
|
||||
services:
|
||||
- postgres:latest
|
||||
- postgres:12
|
||||
variables:
|
||||
POSTGRES_DB: ci
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
|
||||
DB_HOST: postgres
|
||||
before_script:
|
||||
- pip install poetry==1.3.1
|
||||
- poetry config virtualenvs.create false
|
||||
- poetry install --no-interaction --no-ansi
|
||||
script:
|
||||
- python -V
|
||||
- pip install -r requirements.txt
|
||||
- cp sikweb/settings-sample.py sikweb/default_settings.py
|
||||
- cp sikweb/.ci-settings.py sikweb/settings.py
|
||||
- python manage.py migrate --noinput
|
||||
- python manage.py createdefaultadmin
|
||||
- python manage.py test
|
||||
|
||||
pycodestyle:
|
||||
image: python:3.5
|
||||
lint:py:
|
||||
image: python:3.9
|
||||
stage: lint
|
||||
only:
|
||||
- pushes
|
||||
needs: []
|
||||
script:
|
||||
- pip install pycodestyle
|
||||
- pycodestyle --config=setup.cfg --count .
|
||||
- pip install black==22.3.0
|
||||
- black --check .
|
||||
|
||||
eslint:
|
||||
image: node:7.10.0
|
||||
lint:js:
|
||||
image: node:14
|
||||
stage: lint
|
||||
before_script:
|
||||
- npm install
|
||||
only:
|
||||
- pushes
|
||||
needs: ["install"]
|
||||
script:
|
||||
- npm run eslint
|
||||
- npm run lint:js
|
||||
|
||||
remark:
|
||||
image: node:7.10.0
|
||||
lint:md:
|
||||
image: node:14
|
||||
stage: lint
|
||||
before_script:
|
||||
- npm install
|
||||
only:
|
||||
- pushes
|
||||
needs: ["install"]
|
||||
script:
|
||||
- npm run remark
|
||||
- npm run lint:md
|
||||
|
||||
publish:
|
||||
image: docker:stable
|
||||
stage: publish
|
||||
image: docker:latest
|
||||
needs: ["test", "lint:py", "lint:js", "lint:md"]
|
||||
services:
|
||||
- docker:stable-dind
|
||||
only:
|
||||
- develop
|
||||
before_script:
|
||||
- docker info
|
||||
- docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $REGISTRY_URL
|
||||
- master
|
||||
script:
|
||||
- docker info
|
||||
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
|
||||
- docker build . -t "$IMAGE_NAME"
|
||||
- docker push "$IMAGE_NAME"
|
||||
|
||||
deploy_dev:
|
||||
deploy:dev:
|
||||
image: docker:stable
|
||||
stage: deploy
|
||||
image: alpine:latest
|
||||
environment:
|
||||
name: dev
|
||||
url: http://web.sik.party:8000
|
||||
only:
|
||||
- develop
|
||||
before_script:
|
||||
- pwd
|
||||
- apk add --update openssh
|
||||
- ssh -V
|
||||
- mkdir -p ~/.ssh
|
||||
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
|
||||
- chmod 600 ~/.ssh/id_rsa
|
||||
- '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
|
||||
script:
|
||||
- scp docker-compose.yml $DEV_SSH_USER@$DEV_SSH_HOST:~/deployment/docker-compose.yml
|
||||
- scp .deploy_dev.sh $DEV_SSH_USER@$DEV_SSH_HOST:~/deployment/deploy_dev.sh
|
||||
- ssh $DEV_SSH_USER@$DEV_SSH_HOST "bash ~/deployment/deploy_dev.sh \"$IMAGE_NAME\""
|
||||
|
||||
deploy_production:
|
||||
stage: deploy
|
||||
image: alpine:latest
|
||||
environment:
|
||||
name: production
|
||||
url: https://sika.sahkoinsinoorikilta.fi
|
||||
when: manual
|
||||
name: dev
|
||||
url: http://api.dev.sahkoinsinoorikilta.fi
|
||||
variables:
|
||||
DOCKER_HOST: $DEV_CI_DOCKER_HOST
|
||||
DOCKER_TLS_VERIFY: 1
|
||||
before_script:
|
||||
- mkdir -p ~/.docker
|
||||
- echo "$DEV_TLSCACERT" > ~/.docker/ca.pem
|
||||
- echo "$DEV_TLSCERT" > ~/.docker/cert.pem
|
||||
- echo "$DEV_TLSKEY" > ~/.docker/key.pem
|
||||
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
|
||||
script:
|
||||
- docker stack deploy --with-registry-auth -c stack-compose-dev.yml "$SERVICE_NAME"
|
||||
after_script:
|
||||
- docker logout "$CI_REGISTRY"
|
||||
|
||||
deploy:production:
|
||||
stage: deploy
|
||||
image: docker:stable
|
||||
only:
|
||||
- master
|
||||
environment:
|
||||
name: production
|
||||
url: https://api.sahkoinsinoorikilta.fi
|
||||
when: manual
|
||||
variables:
|
||||
DOCKER_HOST: $CI_DOCKER_HOST
|
||||
DOCKER_TLS_VERIFY: 1
|
||||
before_script:
|
||||
- pwd
|
||||
- apk add --update openssh
|
||||
- ssh -V
|
||||
- mkdir -p ~/.ssh
|
||||
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
|
||||
- chmod 600 ~/.ssh/id_rsa
|
||||
- echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
|
||||
- mkdir -p ~/.docker
|
||||
- echo "$TLSCACERT" > ~/.docker/ca.pem
|
||||
- echo "$TLSCERT" > ~/.docker/cert.pem
|
||||
- echo "$TLSKEY" > ~/.docker/key.pem
|
||||
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
|
||||
script:
|
||||
- ssh $PROD_SSH_USER@$PROD_SSH_HOST "zsh ~/deploy.sh"
|
||||
|
||||
- docker stack deploy --with-registry-auth -c stack-compose.yml "$SERVICE_NAME"
|
||||
after_script:
|
||||
- docker logout "$CI_REGISTRY"
|
||||
|
||||
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"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
_
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
PURPLE='\033[0;35m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
. "${VIRTUAL_ENV}/bin/activate"
|
||||
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
printf "${PURPLE}Failed to find virtualenv. Skipping pre-commit hook.\n${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
npm run lint
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
PURPLE='\033[0;35m'
|
||||
NC='\033[0m' # No Color
|
||||
@@ -14,10 +15,8 @@ fi
|
||||
set -e
|
||||
|
||||
printf "${PURPLE}Running pre-push tests.${NC}\n"
|
||||
printf "${PURPLE}npm tests...${NC}\n"
|
||||
npm test
|
||||
printf "${PURPLE}pycodestyle tests...${NC}\n"
|
||||
pycodestyle .
|
||||
printf "${PURPLE}linters...${NC}\n"
|
||||
npm run lint
|
||||
printf "${PURPLE}unit tests...${NC}\n"
|
||||
python -Wall manage.py test --noinput
|
||||
set +e
|
||||
@@ -0,0 +1 @@
|
||||
3.9
|
||||
@@ -1 +0,0 @@
|
||||
global_static
|
||||
@@ -1,8 +1,28 @@
|
||||
FROM python:3
|
||||
FROM python:3.9-slim-buster as builder
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
ENV IS_DOCKER 1
|
||||
RUN mkdir /code
|
||||
WORKDIR /code
|
||||
ADD requirements.txt /code/
|
||||
RUN env
|
||||
ADD . /code/
|
||||
COPY . ./
|
||||
|
||||
ENV POETRY_VERSION=1.3.1
|
||||
|
||||
RUN pip install "poetry==$POETRY_VERSION"
|
||||
RUN poetry export --without-hashes > requirements.txt
|
||||
|
||||
FROM python:3.9-slim-buster as server
|
||||
|
||||
WORKDIR /app
|
||||
COPY . ./
|
||||
COPY --from=builder requirements.txt ./
|
||||
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
# prevents python creating .pyc files
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
# pip
|
||||
PIP_NO_CACHE_DIR=off \
|
||||
PIP_DISABLE_PIP_VERSION_CHECK=on \
|
||||
PIP_DEFAULT_TIMEOUT=100
|
||||
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y build-essential
|
||||
RUN pip install --no-deps -r requirements.txt
|
||||
|
||||
RUN python manage.py collectstatic --noinput
|
||||
CMD ["sh", "-c", "./production_entrypoint.sh"]
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
# Web 2.0 Backend
|
||||
|
||||
[Django](https://www.djangoproject.com/) backend containing multiple small applications and api for Next.js frontend.
|
||||
|
||||
* **Web app:** Backend for the main website.
|
||||
* **Member register:** Data table app for viewing and modifying the member register, member applications and membership payments.
|
||||
* **Kaehmy:** Form for creating and listing kaehmys
|
||||
* **Ohlhafv:** Form for creating and listing ohlhafv challenges.
|
||||
* **Infoscreen:** Angular-based slideshow app for the guild room's screens.
|
||||
## Installation
|
||||
|
||||
Set up your SSH key authentication in GitLab Profile Settings. Then clone the repository and checkout the development branch:
|
||||
|
||||
```bash
|
||||
git clone git@gitlab.com:sahkoinsinoorikilta/vtmk/web2.0-backend.git
|
||||
cd web2.0-backend
|
||||
git checkout develop
|
||||
```
|
||||
|
||||
Copy env file for local use:
|
||||
```bash
|
||||
cp .env.dev .env
|
||||
```
|
||||
|
||||
### Poetry
|
||||
|
||||
For depedencies and virtual environment, we use [poetry](https://python-poetry.org/).
|
||||
|
||||
First install [python](https://wiki.python.org/moin/BeginnersGuide/Download). Then install poetry:
|
||||
|
||||
```bash
|
||||
python3 -m pip install poetry==1.3.1
|
||||
```
|
||||
|
||||
The easiest integration with VSCode is to have poetry install virtual environment in project folder, configured with CMD
|
||||
|
||||
```bash
|
||||
python3 -m poetry config virtualenvs.in-project true
|
||||
```
|
||||
|
||||
### Node
|
||||
|
||||
We use Node.js for few development tasks, like linting. Easiest way to install Node is [nvm](https://github.com/nvm-sh/nvm). After installing install dependencies:
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
TODO: List scripts
|
||||
|
||||
### Database
|
||||
|
||||
To run a local development database **[docker](https://docs.docker.com/engine/install/)** is recommended. If you want to additianally use a db management tool **[pgAdmin](https://www.pgadmin.org/download/)** is nice.
|
||||
|
||||
After installing docker use the following to create a database:
|
||||
```bash
|
||||
docker run --name sik.web.db -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres:12
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
Activate virtual environment in shell
|
||||
|
||||
```bash
|
||||
python3 -m poetry shell
|
||||
```
|
||||
|
||||
Install dependencies
|
||||
|
||||
```bash
|
||||
poetry install
|
||||
```
|
||||
|
||||
### Initializing data
|
||||
|
||||
Run the following `manage.py` commands to initialize a new database. Do not run these in production without thinking!
|
||||
|
||||
```bash
|
||||
python manage.py migrate # run migrations
|
||||
python manage.py createdefaultadmin # creates an admin user
|
||||
python manage.py initialize # creates user groups
|
||||
python manage.py createdummydata # creates dummy members to the member register
|
||||
```
|
||||
|
||||
### Running
|
||||
|
||||
```bash
|
||||
python manage.py runserver
|
||||
```
|
||||
|
||||
Visit [https://localhost:8000](https://localhost:8000) in your browser!
|
||||
|
||||
Using address `0.0.0.0` will bind to all IP addresses. Using `localhost` will only bind to your machine.
|
||||
|
||||
```bash
|
||||
python manage.py runserver 0.0.0.0:8000
|
||||
```
|
||||
|
||||
### Development workflow
|
||||
|
||||
When you start working on a feature, create a feature branch for your changes. These feature branches should be prefixed with `feature`.
|
||||
|
||||
Example of creating a feature branch:
|
||||
|
||||
```bash
|
||||
git checkout -b feature-branch-name
|
||||
```
|
||||
|
||||
When your changes are ready and the code works without errors, submit a merge request to `develop` in GitLab. Another developer reviews your changes and runs the merge. Feature branches should be closed on merge.
|
||||
|
||||
Bugfixes do not need their own feature branches and can be pushed straight to `develop`, but if the fix needs a notable amount of work, it should be done in a `bugfix` branch instead.
|
||||
|
||||
Merge requests to `master` should be reviewed by multiple developers. Only a moderator can accept merge requests to `master`.
|
||||
|
||||
### Linting
|
||||
|
||||
Lint python files using `black` with
|
||||
|
||||
```bash
|
||||
npm run lint:py # check changes
|
||||
npm run lint:py:fix # fix errors
|
||||
```
|
||||
|
||||
Lint javascript and markdown using `eslint` and `remark` with
|
||||
|
||||
```bash
|
||||
npm run lint:md # markdown
|
||||
npm run lint:js # javascript
|
||||
```
|
||||
|
||||
Use an editor with linting capabilities to write pretty code that passes linting. Examples include _VSCode_, _Atom_ and _Pycharm_.
|
||||
|
||||
### Unit tests
|
||||
|
||||
Run unit tests with
|
||||
|
||||
```bash
|
||||
python manage.py test
|
||||
```
|
||||
|
||||
Due to the mostly static nature of the project, most elements are difficult to properly unit test. If you write code with actual logic, make sure to write at least one unit or integration test that tests your code's core functionality.
|
||||
|
||||
Tests are located in `tests.py` under every subproject.
|
||||
|
||||
## Production
|
||||
|
||||
Project is run in production with Docker. See `Dockerfile` for details.
|
||||
|
||||
For more information about deployment check **[infra](https://gitlab.com/sahkoinsinoorikilta/vtmk/infra)** repository.
|
||||
|
||||
## GitLab CI
|
||||
|
||||
All pushed changes go through the GitLab Continuous Integration, which consists of automated unit testing and linting. Make sure your changes pass both before merging to `develop` or `master`.
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
@@ -1,124 +0,0 @@
|
||||
body {
|
||||
background-color: white;
|
||||
font-family: monospace;
|
||||
color: black;
|
||||
}
|
||||
#container{
|
||||
position:relative;
|
||||
width:95%;
|
||||
margin-left:auto;
|
||||
margin-right:auto;
|
||||
height:100%;
|
||||
overflow:hidden;
|
||||
|
||||
}
|
||||
#upper{
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: bottom center;
|
||||
background-image: url("/static/coffee_scale/img/smokes.png");
|
||||
transform-origin: bottom;
|
||||
animation: smokes 8s ease-in-out 0s infinite;
|
||||
opacity:0;
|
||||
height:40%;
|
||||
}
|
||||
#lower{
|
||||
position:relative;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: top center;
|
||||
background-image: url("/static/coffee_scale/img/coffeecup3.png");
|
||||
height:60%;
|
||||
}
|
||||
#scale{
|
||||
position:absolute;
|
||||
top:80%;
|
||||
width:90%;
|
||||
height:10%;
|
||||
margin: 0% 5% 0% 5%;
|
||||
background: lightgrey;
|
||||
border-radius: 10px;
|
||||
overflow:hidden;
|
||||
}
|
||||
#scale2{
|
||||
width: 0%;
|
||||
transition: width 2s;
|
||||
height:100%;
|
||||
background: green;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.brewtime{
|
||||
text-align:right;
|
||||
position:absolute;
|
||||
right:0px;
|
||||
z-index:5;
|
||||
font-size:10vw;
|
||||
}
|
||||
#address{
|
||||
text-align:left;
|
||||
position:absolute;
|
||||
left:0px;
|
||||
z-index:5;
|
||||
font-size:4vw;
|
||||
color: #333;
|
||||
}
|
||||
.layertwo{
|
||||
display: None;
|
||||
}
|
||||
noscript{
|
||||
color:red;
|
||||
}
|
||||
.text{
|
||||
color:green;
|
||||
position:absolute;
|
||||
top:50%;
|
||||
left:50%;
|
||||
}
|
||||
.brewing{
|
||||
animation: brewing 5s ease-in-out 0s infinite;
|
||||
}
|
||||
.hurry{
|
||||
color:red !important;
|
||||
}
|
||||
.unknown{
|
||||
color:orange !important;
|
||||
animation: unknown 5s ease-in-out 0s infinite;
|
||||
}
|
||||
.friday{
|
||||
animation: friday 20s ease-in-out 0s infinite;
|
||||
}
|
||||
.normal{
|
||||
animation: normal 1000s ease-in-out 0s infinite;
|
||||
}
|
||||
.coffeeready{
|
||||
animation: coffeeready 10s ease-in-out 0s;
|
||||
}
|
||||
@keyframes smokes {
|
||||
0% {transform: skewX(-10deg);}
|
||||
50% {transform: skewX(10deg);}
|
||||
100% {transform: skewX(-10deg);}
|
||||
}
|
||||
@keyframes brewing {
|
||||
0% {color:green;}
|
||||
50% {color: transparent;}
|
||||
100% {color:green;}
|
||||
}
|
||||
@keyframes coffeeready {
|
||||
0% {background-color:white;}
|
||||
25% {background-color:rgb(100, 255, 100);}
|
||||
50% {background-color:white;}
|
||||
75% {background-color:rgb(100, 255, 100);}
|
||||
100% {background-color:white;}
|
||||
}
|
||||
@keyframes unknown {
|
||||
0%,40% {transform: rotate(0deg);}
|
||||
60%,100% {transform: rotate(360deg);}
|
||||
}
|
||||
@keyframes friday {
|
||||
0% {transform: rotate(0deg);}
|
||||
100% {transform: rotate(360deg);}
|
||||
}
|
||||
@keyframes normal {
|
||||
0%,49% {transform: rotate(0deg);}
|
||||
50%,100% {transform: rotate(360deg);}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 9.0 KiB |
@@ -1,183 +0,0 @@
|
||||
//Inner state
|
||||
var lastBrew = new Date(0);
|
||||
var brewing = false;
|
||||
var backoff = 2000;
|
||||
|
||||
//MQTT client config
|
||||
var username = "coffee-user-"+ Math.random();
|
||||
// eslint-disable-next-line no-undef
|
||||
var client = new Paho.MQTT.Client("sika.sahkoinsinoorikilta.fi", 9001, username);
|
||||
client.onMessageArrived = function (message) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("Topic: "+message.destinationName+" msg: "+message.payloadString);
|
||||
var ev = new CustomEvent(message.destinationName, {'detail': message.payloadString});
|
||||
window.dispatchEvent(ev);
|
||||
}
|
||||
|
||||
function reconnect(responseObject){
|
||||
if (responseObject.errorCode !== 0) {
|
||||
console.log("connection lost! Reason: "+responseObject.errorMessage); // eslint-disable-line no-console
|
||||
setTimeout(function(){
|
||||
client.connect({onSuccess:onConnect, useSSL:true, onFailure: reconnect});
|
||||
}, backoff);
|
||||
}
|
||||
}
|
||||
|
||||
function onConnect() {
|
||||
console.log("MQTT connected"); // eslint-disable-line no-console
|
||||
//set and reset reconnector
|
||||
client.onConnectionLost = reconnect
|
||||
// subscribe to topics
|
||||
client.subscribe("sik/kiltahuone/kahvivaaka/cups");
|
||||
client.subscribe("sik/kiltahuone/kahvivaaka/brewing");
|
||||
client.subscribe("sik/kiltahuone/kahvivaaka/brewtime");
|
||||
}
|
||||
|
||||
// data update and parse functions
|
||||
function parseCups(ev){
|
||||
var cups = parseFloat(ev.detail).toFixed(1)
|
||||
|
||||
function makeEvent(cups) {
|
||||
return (String(cups) !== '-1.0')
|
||||
? new CustomEvent("cupsChanged", {'detail': cups})
|
||||
: new CustomEvent("cupsError", {'detail': 'Error: unable to fetch cups :('});
|
||||
}
|
||||
|
||||
window.dispatchEvent(makeEvent(cups));
|
||||
}
|
||||
function updateCups(ev){
|
||||
$("#text").text(ev.detail);
|
||||
}
|
||||
function showCupsError(ev) {
|
||||
$('#text').text(ev.detail);
|
||||
$('#text').css({
|
||||
'font-size': '7vh',
|
||||
'left': '0',
|
||||
'top': '40%',
|
||||
'width': '100%',
|
||||
'text-align': 'center',
|
||||
'color': 'red',
|
||||
});
|
||||
$('#lower').css({'background-image': 'none'});
|
||||
}
|
||||
function updateScale(ev){
|
||||
$("#scale2").css({width: Math.min(ev.detail/9*100,100) + '%'});
|
||||
}
|
||||
|
||||
function tick(){
|
||||
var ev = new CustomEvent("tick", {'detail': new Date()});
|
||||
window.dispatchEvent(ev);
|
||||
}
|
||||
|
||||
function updateTime(ev){
|
||||
var now = ev.detail;
|
||||
$("#time").html(formatTime(now.getHours(),now.getMinutes(),now.getSeconds()));
|
||||
}
|
||||
|
||||
function coffeeLowEffect(ev){
|
||||
ev.detail <= 2 ? $("#text").addClass("hurry") : $("#text").removeClass("hurry");
|
||||
}
|
||||
function coffeeReadyEffect(){
|
||||
$("body").addClass("coffeeready");
|
||||
// autoclear animation class in 10s
|
||||
setTimeout(function(){$("body").removeClass("coffeeready");}, 10000);
|
||||
}
|
||||
function hotEffect(ev){
|
||||
var opa = Math.max(100 - ev.detail / 90000,0);
|
||||
$("#upper").css({opacity: opa/100});
|
||||
}
|
||||
function brewAnimStart(){
|
||||
$(".text").addClass("brewing");
|
||||
$(".layerone").hide();
|
||||
$(".layertwo").show();
|
||||
}
|
||||
function brewAnimEnd(){
|
||||
$(".text").removeClass("brewing");
|
||||
$(".layertwo").hide();
|
||||
$(".layerone").show();
|
||||
}
|
||||
function brewNotifier(ev){
|
||||
var new_brewing = parseInt(ev.detail);
|
||||
if (new_brewing == 1 && brewing == 0){
|
||||
window.dispatchEvent(new Event("brewStart"));
|
||||
} else if (new_brewing == 0 && brewing == 1){
|
||||
window.dispatchEvent(new Event("brewEnd"));
|
||||
}
|
||||
brewing = new_brewing;
|
||||
}
|
||||
function brewTimeParser(ev){
|
||||
lastBrew = new Date(parseInt(ev.detail)*1000.0);
|
||||
}
|
||||
function updateBrewDiff(){
|
||||
var now = new Date();
|
||||
var timeDiff = Math.max(now.getTime() - lastBrew.getTime(), 0);
|
||||
var eve = new CustomEvent("dtUpdate", {'detail': timeDiff});
|
||||
window.dispatchEvent(eve);
|
||||
}
|
||||
function updateBrewTime(ev){
|
||||
var timeDiff = ev.detail;
|
||||
var timeStr;
|
||||
if (timeDiff < 3600000){
|
||||
timeStr = Math.round(timeDiff / 60000) + ' min'
|
||||
} else if (timeDiff < 10000* 3600 * 1000){ // 1000h
|
||||
timeStr = '~' + Math.round(timeDiff / 3600000 * 2) / 2 + ' h';
|
||||
} else {
|
||||
timeStr = "???"
|
||||
}
|
||||
$("#brewtime").html(timeStr);
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
function nToS(num){
|
||||
return num < 10 ? "0" + num : "" + num;
|
||||
}
|
||||
|
||||
function formatTime(hours, minutes, seconds){
|
||||
return nToS(hours)+":"+nToS(minutes)+":"+nToS(seconds)
|
||||
}
|
||||
|
||||
function resize(){
|
||||
var w = $("#container").width();
|
||||
var h = $("#container").height();
|
||||
var s = w > h ? h : w;
|
||||
var font = s * 0.8 * 0.38/Math.sqrt(3);
|
||||
$(".text").css({ top: s*0.16-font/2 + 'px',
|
||||
fontSize: font + 'px',
|
||||
marginLeft: -font*3*3/10 + 'px'});
|
||||
}
|
||||
|
||||
// Init everything
|
||||
|
||||
$(document).ready(function(){
|
||||
client.connect({onSuccess:onConnect, useSSL:true, onFailure:reconnect});
|
||||
|
||||
//connect MQTT event listeners
|
||||
window.addEventListener("sik/kiltahuone/kahvivaaka/cups", parseCups);
|
||||
window.addEventListener("sik/kiltahuone/kahvivaaka/brewing", brewNotifier);
|
||||
window.addEventListener("sik/kiltahuone/kahvivaaka/brewtime", brewTimeParser);
|
||||
|
||||
//connect other event listeners
|
||||
window.addEventListener("cupsChanged", updateCups);
|
||||
window.addEventListener("cupsChanged", coffeeLowEffect);
|
||||
window.addEventListener("cupsChanged", updateScale);
|
||||
window.addEventListener("cupsChanged", resize);
|
||||
|
||||
window.addEventListener("cupsError", showCupsError);
|
||||
window.addEventListener("cupsError", coffeeLowEffect);
|
||||
window.addEventListener("cupsError", updateScale);
|
||||
|
||||
window.addEventListener("brewStart", brewAnimStart);
|
||||
window.addEventListener("brewEnd", brewAnimEnd);
|
||||
window.addEventListener("brewEnd", coffeeReadyEffect);
|
||||
window.addEventListener("tick", updateTime);
|
||||
window.addEventListener("tick", updateBrewDiff);
|
||||
window.addEventListener("dtUpdate", updateBrewTime);
|
||||
window.addEventListener("dtUpdate", hotEffect);
|
||||
|
||||
//start time based events
|
||||
setInterval(tick, 100);
|
||||
tick();
|
||||
|
||||
});
|
||||
$(window).resize(resize);
|
||||
@@ -1,41 +0,0 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Coffee Cups @Guild Room - AYY SIK ry</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="3600">
|
||||
<meta http-equiv="cache-control" content="max-age=0" />
|
||||
<meta http-equiv="cache-control" content="no-cache" />
|
||||
<meta http-equiv="expires" content="0" />
|
||||
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
|
||||
<meta http-equiv="pragma" content="no-cache" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.2/mqttws31.js"
|
||||
type="text/javascript"></script>
|
||||
<link rel="stylesheet" href="{% static "coffee_scale/css/coffee.css" %}">
|
||||
<script src="{% static "coffee_scale/js/coffee.js" %}"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="container">
|
||||
<span id="brewtime" class="brewtime layerone"></span>
|
||||
<span class="brewtime layertwo">:)</span>
|
||||
<span id="address">
|
||||
ka.dy.fi
|
||||
<noscript><br>This page uses JavaScript!</noscript>
|
||||
<br><span id="time"></span></span>
|
||||
</span>
|
||||
<div id="upper">
|
||||
</div>
|
||||
<!--Kahvinkeitin on rikki. Varakeittimellä keitettyä kahvia saattaa olla.-->
|
||||
<div id="lower" class="normal">
|
||||
<div id="text" class="text layerone">???</div>
|
||||
<div class="text layertwo"> +</div>
|
||||
<div id="scale"><div id="scale2"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,2 +0,0 @@
|
||||
from django.test import TestCase, Client
|
||||
from django.conf import settings
|
||||
@@ -1,12 +0,0 @@
|
||||
from django.conf.urls import url
|
||||
from django.conf import settings
|
||||
from .views import coffee_view
|
||||
|
||||
urlpatterns = [
|
||||
# landing page
|
||||
url(r'^$', coffee_view),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
urlpatterns += staticfiles_urlpatterns()
|
||||
@@ -1,11 +0,0 @@
|
||||
from django.shortcuts import render
|
||||
from django.http import JsonResponse
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
import logging
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
def coffee_view(request):
|
||||
return render(request, 'coffee_scale:coffee.html')
|
||||
@@ -2,12 +2,23 @@ version: '3'
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres
|
||||
image: postgres:12
|
||||
volumes:
|
||||
- dbdata:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
|
||||
web:
|
||||
build: .
|
||||
image: git.sahkoinsinoorikilta.fi:4567/vtmk/web2.0
|
||||
command: ["bash", "-c", "cd /code && ./wait-for-it.sh db:5432 -- bash setup.sh --no-input --no-npm && python manage.py runserver 0.0.0.0:8000"]
|
||||
image: registry.gitlab.com/sahkoinsinoorikilta/vtmk/web2.0-backend
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "8000:8000"
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
volumes:
|
||||
dbdata:
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
# Ilmotunkki
|
||||
|
||||
## Terms
|
||||
- Signup, Form with collection of questions
|
||||
- Response, One answer to some signup
|
||||
- Quota, Amount of people allowed to respond with some option selected.
|
||||
- In generic case there is no option and quota is just max number of people.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Officials may generate signups forms
|
||||
- Officials may see results from signups
|
||||
- Officials may see some stats from their signups
|
||||
- for example distributions of multiple choice answers
|
||||
- Officials should be able to edit signups wherever possible
|
||||
- Propably not possible to edit after first response
|
||||
- Officials should be able to delete responses
|
||||
- Officials should be able to embed payment information to the signup?
|
||||
- TODO: is there need for unique reference numbers for every response?
|
||||
- Officials should be able to save a signup to a reusable template.
|
||||
- Possibility to save templates?
|
||||
|
||||
- Signup may be attached to an event
|
||||
- Multiple signups to a single event should be possible (FTMK uses for museum visits? Erna asked if it was possible in old web)
|
||||
- Possibility for external service (Google Form, URL will suffice)
|
||||
|
||||
- Signup should support custom quotas
|
||||
- Atleast quotas from multiple choices and checkboxes
|
||||
- Text quotas are risky (typos everywhere!!)
|
||||
- Signup should have start and end times
|
||||
- Signup should support atleast following questiontypes
|
||||
- Text
|
||||
- multiple choice (select one)
|
||||
- checkbox (boolean yes/no)
|
||||
|
||||
- Signup should support reserve slots.
|
||||
- TODO: quota based reserves or generic? or both?
|
||||
|
||||
- Responding should send confirm email
|
||||
- Response should be editable by responder and only by the responder until the closing of the signup
|
||||
- TODO: is there need to custom edit period or disable?
|
||||
|
||||
- Responders should see amount of quotas left.
|
||||
- Responders should see some information about other responses
|
||||
- TODO: names? should this be editable by officials?
|
||||
- Or superadmin can edit and the one signing up within edit period
|
||||
- NOTE: Quota related info is exposed if any info is printed
|
||||
- When quotas need to be hidden? PoTa?
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@@ -1,5 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class IlmotunkkiConfig(AppConfig):
|
||||
name = 'ilmotunkki'
|
||||
@@ -1,16 +0,0 @@
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
class Signup(models.Model):
|
||||
start = models.DateTimeField()
|
||||
end = models.DateTimeField()
|
||||
|
||||
|
||||
class Question(models.Model):
|
||||
pass
|
||||
|
||||
|
||||
class Answer(models.Model):
|
||||
signup = models.ForeignKey(Signup, on_delete=models.CASCADE)
|
||||
question = models.ForeignKey(Question, on_delete=models.PROTECT)
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
@@ -2,8 +2,15 @@
|
||||
|
||||
from django.contrib import admin
|
||||
from infoscreen.models import (
|
||||
Rotation, InfoItem, InfoInstance, ImageInfoItem,
|
||||
ExternalImageInfoItem, ABBInfoItem, ExternalWebsiteInfoItem, VideoInfoItem)
|
||||
Rotation,
|
||||
InfoItem,
|
||||
InfoInstance,
|
||||
ImageInfoItem,
|
||||
ExternalImageInfoItem,
|
||||
ABBInfoItem,
|
||||
ExternalWebsiteInfoItem,
|
||||
VideoInfoItem,
|
||||
)
|
||||
|
||||
# Register your models here.
|
||||
admin.site.register(Rotation)
|
||||
|
||||
@@ -6,4 +6,4 @@ from django.apps import AppConfig
|
||||
class InfoscreenConfig(AppConfig):
|
||||
"""Infoscreen app configuration."""
|
||||
|
||||
name = 'infoscreen'
|
||||
name = "infoscreen"
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
"""File containing Infoscreen HSL data fetcher classes."""
|
||||
|
||||
import requests
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import pytz
|
||||
|
||||
from datetime import timedelta, datetime
|
||||
from django.utils import timezone, dateparse
|
||||
from django.utils.dateformat import format
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
with open(os.path.join(settings.BASE_DIR, 'infoscreen', 'hsl_stops.graphql')) as stops_file:
|
||||
STOPS_QUERY = stops_file.read()
|
||||
|
||||
with open(os.path.join(settings.BASE_DIR, 'infoscreen', 'hsl_stops_variables.json')) as vars_file:
|
||||
STOPS_VARS = json.loads(vars_file.read())
|
||||
|
||||
API_URL = 'https://api.digitransit.fi/routing/v1/routers/hsl/index/graphql'
|
||||
API_HEADERS = {'Content-Type': 'application/json'}
|
||||
|
||||
|
||||
def fetch():
|
||||
"""Fetch data from HSL API."""
|
||||
|
||||
query_vars = STOPS_VARS.copy()
|
||||
query_vars['startTime_6'] = format(timezone.now(), 'U')
|
||||
|
||||
post_data = json.dumps({
|
||||
'operationName': 'NearestRoutesContainer',
|
||||
'query': STOPS_QUERY,
|
||||
'variables': query_vars,
|
||||
})
|
||||
|
||||
resp = requests.post(API_URL, data=post_data, headers=API_HEADERS)
|
||||
|
||||
data = resp.json()
|
||||
|
||||
items = data['data']['viewer']['_nearest']['edges']
|
||||
places = map(lambda item: item['node']['place'], items)
|
||||
|
||||
schedule = []
|
||||
for place in places:
|
||||
route = place['pattern']['route']['shortName']
|
||||
stop_times = place['_stoptimes']
|
||||
for stop_time in stop_times:
|
||||
timestamp = time_utc = stop_time['serviceDay'] + stop_time['realtimeArrival']
|
||||
headsign = stop_time['stopHeadsign']
|
||||
stop_name = stop_time['stop']['name']
|
||||
time_diff = (timestamp - timezone.now().timestamp()) / 60 # minutes
|
||||
|
||||
if time_diff < settings.HSL_DEPARTURE_THRESHOLD:
|
||||
continue
|
||||
elif time_diff < settings.HSL_HURRY_THRESHOLD:
|
||||
time = '{} min'.format(int(time_diff))
|
||||
else:
|
||||
time = pytz.utc.localize(datetime.fromtimestamp(timestamp)).strftime('%H:%M')
|
||||
|
||||
schedule.append({
|
||||
'route': route,
|
||||
'headsign': headsign,
|
||||
'timestamp': time,
|
||||
'stop': stop_name,
|
||||
'utc': time_utc,
|
||||
})
|
||||
|
||||
return schedule
|
||||
@@ -1,71 +0,0 @@
|
||||
query NearestRoutesContainer($lat_0: Float!, $lon_1: Float!, $maxDistance_2: Int!, $maxResults_3: Int!, $timeRange_7: Int!, $numberOfDepartures_8: Int!, $filterByModes_4: [Mode]!, $filterByPlaceTypes_5: [FilterPlaceType]!, $startTime_6: Long!) {
|
||||
viewer {
|
||||
...F5
|
||||
}
|
||||
}
|
||||
|
||||
fragment F0 on DepartureRow {
|
||||
_stoptimes4caEfh: stoptimes(startTime: $startTime_6, timeRange: $timeRange_7, numberOfDepartures: $numberOfDepartures_8) {
|
||||
pickupType
|
||||
serviceDay
|
||||
realtimeDeparture
|
||||
}
|
||||
id
|
||||
}
|
||||
|
||||
fragment F1 on DepartureRow {
|
||||
pattern {
|
||||
route {
|
||||
shortName
|
||||
}
|
||||
}
|
||||
_stoptimes: stoptimes(startTime: $startTime_6, timeRange: $timeRange_7, numberOfDepartures: $numberOfDepartures_8) {
|
||||
realtimeArrival
|
||||
serviceDay
|
||||
stopHeadsign
|
||||
stop {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment F2 on BikeRentalStation {
|
||||
id
|
||||
}
|
||||
|
||||
fragment F3 on placeAtDistance {
|
||||
distance
|
||||
place {
|
||||
id
|
||||
__typename
|
||||
...F1
|
||||
...F2
|
||||
}
|
||||
id
|
||||
}
|
||||
|
||||
fragment F4 on placeAtDistanceConnection {
|
||||
edges {
|
||||
node {
|
||||
distance
|
||||
place {
|
||||
id
|
||||
__typename
|
||||
...F0
|
||||
}
|
||||
id
|
||||
...F3
|
||||
}
|
||||
cursor
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
}
|
||||
}
|
||||
|
||||
fragment F5 on QueryType {
|
||||
_nearest: nearest(lat: $lat_0, lon: $lon_1, maxDistance: $maxDistance_2, maxResults: $maxResults_3, first: $maxResults_3, filterByModes: $filterByModes_4, filterByPlaceTypes: $filterByPlaceTypes_5) {
|
||||
...F4
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"lat_0": 60.190480099999995,
|
||||
"lon_1": 24.8275665,
|
||||
"maxDistance_2": 1000,
|
||||
"maxResults_3": 50,
|
||||
"numberOfDepartures_8": 2,
|
||||
"timeRange_7": 7200,
|
||||
"filterByModes_4": ["BUS"],
|
||||
"filterByPlaceTypes_5": ["DEPARTURE_ROW"]
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from infoscreen.hsl_fetcher import HSLFetcher
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Loads HSL timetables and save to json file.'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
fetcher = HSLFetcher()
|
||||
fetcher.fetch()
|
||||
@@ -11,81 +11,173 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
("contenttypes", "0002_remove_content_type_name"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='HSLDataModel',
|
||||
name="HSLDataModel",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('data', models.TextField(default='', editable=False)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("data", models.TextField(default="", editable=False)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='InfoInstance',
|
||||
name="InfoInstance",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('duration', models.FloatField(default=15.0)),
|
||||
('item_id', models.PositiveIntegerField()),
|
||||
('item_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("duration", models.FloatField(default=15.0)),
|
||||
("item_id", models.PositiveIntegerField()),
|
||||
(
|
||||
"item_type",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="contenttypes.ContentType",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='InfoItem',
|
||||
name="InfoItem",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('expire_date', models.DateTimeField(blank=True, null=True)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=255)),
|
||||
("expire_date", models.DateTimeField(blank=True, null=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Rotation',
|
||||
name="Rotation",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=255)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ABBInfoItem',
|
||||
name="ABBInfoItem",
|
||||
fields=[
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
],
|
||||
bases=('infoscreen.infoitem',),
|
||||
bases=("infoscreen.infoitem",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ExternalImageInfoItem',
|
||||
name="ExternalImageInfoItem",
|
||||
fields=[
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
('url', models.TextField()),
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
("url", models.TextField()),
|
||||
],
|
||||
bases=('infoscreen.infoitem',),
|
||||
bases=("infoscreen.infoitem",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HslInfoItem',
|
||||
name="HslInfoItem",
|
||||
fields=[
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
],
|
||||
bases=('infoscreen.infoitem',),
|
||||
bases=("infoscreen.infoitem",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ImageInfoItem',
|
||||
name="ImageInfoItem",
|
||||
fields=[
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
('img', models.ImageField(upload_to='infoimages/')),
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
("img", models.ImageField(upload_to="infoimages/")),
|
||||
],
|
||||
bases=('infoscreen.infoitem',),
|
||||
bases=("infoscreen.infoitem",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SossoInfoItem',
|
||||
name="SossoInfoItem",
|
||||
fields=[
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
],
|
||||
bases=('infoscreen.infoitem',),
|
||||
bases=("infoscreen.infoitem",),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='infoinstance',
|
||||
name='rotation',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='instances', to='infoscreen.Rotation'),
|
||||
model_name="infoinstance",
|
||||
name="rotation",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="instances",
|
||||
to="infoscreen.Rotation",
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -9,15 +9,25 @@ import django.db.models.deletion
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('infoscreen', '0001_initial'),
|
||||
("infoscreen", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CoffeeInfoItem',
|
||||
name="CoffeeInfoItem",
|
||||
fields=[
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
],
|
||||
bases=('infoscreen.infoitem',),
|
||||
bases=("infoscreen.infoitem",),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -9,33 +9,63 @@ import django.db.models.deletion
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('infoscreen', '0002_coffeeinfoitem'),
|
||||
("infoscreen", "0002_coffeeinfoitem"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ApyInfoItem',
|
||||
name="ApyInfoItem",
|
||||
fields=[
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
],
|
||||
bases=('infoscreen.infoitem',),
|
||||
bases=("infoscreen.infoitem",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EventInfoItem',
|
||||
name="EventInfoItem",
|
||||
fields=[
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
],
|
||||
bases=('infoscreen.infoitem',),
|
||||
bases=("infoscreen.infoitem",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ExternalWebsiteInfoItem',
|
||||
name="ExternalWebsiteInfoItem",
|
||||
fields=[
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
('url', models.TextField()),
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
("url", models.TextField()),
|
||||
],
|
||||
bases=('infoscreen.infoitem',),
|
||||
bases=("infoscreen.infoitem",),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='CoffeeInfoItem',
|
||||
name="CoffeeInfoItem",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -9,16 +9,26 @@ import django.db.models.deletion
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('infoscreen', '0003_auto_20170329_1857'),
|
||||
("infoscreen", "0003_auto_20170329_1857"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='VideoInfoItem',
|
||||
name="VideoInfoItem",
|
||||
fields=[
|
||||
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
|
||||
('video', models.FileField(upload_to='infovideos/')),
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
("video", models.FileField(upload_to="infovideos/")),
|
||||
],
|
||||
bases=('infoscreen.infoitem',),
|
||||
bases=("infoscreen.infoitem",),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -8,18 +8,18 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('infoscreen', '0004_videoinfoitem'),
|
||||
("infoscreen", "0004_videoinfoitem"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='externalimageinfoitem',
|
||||
name='url',
|
||||
model_name="externalimageinfoitem",
|
||||
name="url",
|
||||
field=models.URLField(),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='externalwebsiteinfoitem',
|
||||
name='url',
|
||||
model_name="externalwebsiteinfoitem",
|
||||
name="url",
|
||||
field=models.URLField(),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -8,11 +8,14 @@ from django.db import migrations
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('infoscreen', '0005_auto_20170913_1841'),
|
||||
("infoscreen", "0005_auto_20170913_1841"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='HSLDataModel',
|
||||
name="HSLDataModel",
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name="HslInfoItem",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# Generated by Django 2.1.5 on 2019-03-26 12:49
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("infoscreen", "0006_delete_hsldatamodel"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="LunchItem",
|
||||
fields=[
|
||||
(
|
||||
"infoitem_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="infoscreen.InfoItem",
|
||||
),
|
||||
),
|
||||
],
|
||||
bases=("infoscreen.infoitem",),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 3.2.14 on 2022-08-01 19:13
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("infoscreen", "0007_lunchitem"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="infoinstance",
|
||||
name="id",
|
||||
field=models.AutoField(primary_key=True, serialize=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="infoitem",
|
||||
name="id",
|
||||
field=models.AutoField(primary_key=True, serialize=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="rotation",
|
||||
name="id",
|
||||
field=models.AutoField(primary_key=True, serialize=False),
|
||||
),
|
||||
]
|
||||
@@ -7,7 +7,7 @@ from django import forms
|
||||
from django.utils import timezone
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
|
||||
class InfoItem(models.Model):
|
||||
@@ -16,6 +16,7 @@ class InfoItem(models.Model):
|
||||
class __meta__:
|
||||
abstract = True
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = models.CharField(max_length=255)
|
||||
# expire_date = None means never expiring item
|
||||
expire_date = models.DateTimeField(blank=True, null=True)
|
||||
@@ -23,14 +24,14 @@ class InfoItem(models.Model):
|
||||
|
||||
def get_template_url(self):
|
||||
"""Get infoscreen template url."""
|
||||
raise NotImplementedError(
|
||||
"inheriting classes must implement get_template_url")
|
||||
raise NotImplementedError("inheriting classes must implement get_template_url")
|
||||
|
||||
@staticmethod
|
||||
def get_create_template_url():
|
||||
"""Get create infoscreen template url command."""
|
||||
raise NotImplementedError(
|
||||
"inheriting classes must implement get_create_template_url")
|
||||
"inheriting classes must implement get_create_template_url"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def create_from_dict(cls, d):
|
||||
@@ -42,14 +43,13 @@ class InfoItem(models.Model):
|
||||
def update_from_dict(self, d):
|
||||
"""Update model based on given dict."""
|
||||
try:
|
||||
expire_date = d.pop('expire_date', None)
|
||||
self.expire_date = datetime.strptime(
|
||||
expire_date, "%Y-%m-%d %H:%M:%S")
|
||||
expire_date = d.pop("expire_date", None)
|
||||
self.expire_date = datetime.strptime(expire_date, "%Y-%m-%d %H:%M:%S")
|
||||
except:
|
||||
pass
|
||||
|
||||
dmap = {
|
||||
'name': 'name',
|
||||
"name": "name",
|
||||
}
|
||||
for k, v in d.items():
|
||||
try:
|
||||
@@ -61,13 +61,13 @@ class InfoItem(models.Model):
|
||||
def get_dict(self):
|
||||
"""Convert django model to dict and return it."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
'item_type': ContentType.objects.get_for_model(self).id,
|
||||
'template_url': self.get_template_url(),
|
||||
'display_name': self.display_name,
|
||||
'create_template_url': self.get_create_template_url(),
|
||||
'options': {}
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"item_type": ContentType.objects.get_for_model(self).id,
|
||||
"template_url": self.get_template_url(),
|
||||
"display_name": self.display_name,
|
||||
"create_template_url": self.get_create_template_url(),
|
||||
"options": {},
|
||||
}
|
||||
|
||||
def delete(self):
|
||||
@@ -75,8 +75,8 @@ class InfoItem(models.Model):
|
||||
# since generic foreign keys suck, delete info
|
||||
# items pointing here manually
|
||||
InfoInstance.objects.filter(
|
||||
item_id=self.id,
|
||||
item_type=ContentType.objects.get_for_model(self)).delete()
|
||||
item_id=self.id, item_type=ContentType.objects.get_for_model(self)
|
||||
).delete()
|
||||
super().delete()
|
||||
|
||||
@classmethod
|
||||
@@ -139,7 +139,7 @@ class ExternalWebsiteInfoItem(InfoItem):
|
||||
def get_dict(self):
|
||||
"""Convert django model to dict and return it."""
|
||||
d = super().get_dict()
|
||||
d["options"] = {'url': self.url}
|
||||
d["options"] = {"url": self.url}
|
||||
return d
|
||||
|
||||
@classmethod
|
||||
@@ -152,23 +152,22 @@ class ExternalWebsiteInfoItem(InfoItem):
|
||||
def get_list(self):
|
||||
"""Return list containing infoitem data."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
'url': self.url,
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"url": self.url,
|
||||
}
|
||||
|
||||
def update_from_dict(self, d):
|
||||
"""Update model based on given dict."""
|
||||
try:
|
||||
expire_date = d.pop('expire_date', None)
|
||||
self.expire_date = datetime.strptime(
|
||||
expire_date, "%Y-%m-%d %H:%M:%S")
|
||||
expire_date = d.pop("expire_date", None)
|
||||
self.expire_date = datetime.strptime(expire_date, "%Y-%m-%d %H:%M:%S")
|
||||
except:
|
||||
pass
|
||||
|
||||
dmap = {
|
||||
'name': 'name',
|
||||
'url': 'url',
|
||||
"name": "name",
|
||||
"url": "url",
|
||||
}
|
||||
for k, v in d.items():
|
||||
try:
|
||||
@@ -193,6 +192,19 @@ class SossoInfoItem(InfoItem):
|
||||
return "/static/infoscreen/html/sosso_create.html"
|
||||
|
||||
|
||||
class LunchItem(InfoItem):
|
||||
"""Class for Lunch Infoscreen item."""
|
||||
|
||||
display_name = _("Today's lunch")
|
||||
|
||||
def get_template_url(self):
|
||||
return "/static/infoscreen/html/lunch.html"
|
||||
|
||||
@staticmethod
|
||||
def get_create_template_url():
|
||||
return "/static/infoscreen/html/lunch_create.html"
|
||||
|
||||
|
||||
class EventInfoItem(InfoItem):
|
||||
"""Class for Event Infoscreen item."""
|
||||
|
||||
@@ -228,14 +240,14 @@ class ImageInfoItem(InfoItem):
|
||||
def get_dict(self):
|
||||
"""Convert django model to dict and return it."""
|
||||
d = super().get_dict()
|
||||
d["options"] = {'img': self.img.url}
|
||||
d["options"] = {"img": self.img.url}
|
||||
return d
|
||||
|
||||
|
||||
class VideoInfoItem(InfoItem):
|
||||
"""Class for Video Infoscreen item."""
|
||||
|
||||
display_name = ("Video")
|
||||
display_name = "Video"
|
||||
video = models.FileField(upload_to="infovideos/")
|
||||
|
||||
def get_template_url(self):
|
||||
@@ -250,25 +262,10 @@ class VideoInfoItem(InfoItem):
|
||||
def get_dict(self):
|
||||
"""Convert django model to dict and return it."""
|
||||
d = super().get_dict()
|
||||
d["options"] = {'video': self.video.url}
|
||||
d["options"] = {"video": self.video.url}
|
||||
return d
|
||||
|
||||
|
||||
class HslInfoItem(InfoItem):
|
||||
"""Class for HSL Infoscreen item."""
|
||||
|
||||
display_name = _("HSL timetables")
|
||||
|
||||
def get_template_url(self):
|
||||
"""Return HSL infoitem template url."""
|
||||
return "/static/infoscreen/html/hsl.html"
|
||||
|
||||
@staticmethod
|
||||
def get_create_template_url():
|
||||
"""Call create HSL infoitem template url command."""
|
||||
return "/static/infoscreen/html/hsl_create.html"
|
||||
|
||||
|
||||
class ExternalImageInfoItem(InfoItem):
|
||||
"""Class for External Image Infoscreen item."""
|
||||
|
||||
@@ -287,7 +284,7 @@ class ExternalImageInfoItem(InfoItem):
|
||||
def get_dict(self):
|
||||
"""Convert django model to dict and return it."""
|
||||
d = super().get_dict()
|
||||
d["options"] = {'img': self.url}
|
||||
d["options"] = {"img": self.url}
|
||||
return d
|
||||
|
||||
@classmethod
|
||||
@@ -300,15 +297,14 @@ class ExternalImageInfoItem(InfoItem):
|
||||
def update_from_dict(self, d):
|
||||
"""Update model based on given dict."""
|
||||
try:
|
||||
expire_date = d.pop('expire_date', None)
|
||||
self.expire_date = datetime.strptime(
|
||||
expire_date, "%Y-%m-%d %H:%M:%S")
|
||||
expire_date = d.pop("expire_date", None)
|
||||
self.expire_date = datetime.strptime(expire_date, "%Y-%m-%d %H:%M:%S")
|
||||
except:
|
||||
pass
|
||||
|
||||
dmap = {
|
||||
'name': 'name',
|
||||
'url': 'url',
|
||||
"name": "name",
|
||||
"url": "url",
|
||||
}
|
||||
for k, v in d.items():
|
||||
try:
|
||||
@@ -321,12 +317,15 @@ class ExternalImageInfoItem(InfoItem):
|
||||
class InfoInstance(models.Model):
|
||||
"""Class for Info instance in Infoscreen."""
|
||||
|
||||
rotation = models.ForeignKey('Rotation', related_name='instances', on_delete=models.CASCADE)
|
||||
id = models.AutoField(primary_key=True)
|
||||
rotation = models.ForeignKey(
|
||||
"Rotation", related_name="instances", on_delete=models.CASCADE
|
||||
)
|
||||
duration = models.FloatField(default=15.0) # seconds
|
||||
# generic relation to some kind of InfoItem
|
||||
item_id = models.PositiveIntegerField()
|
||||
item_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||
item = GenericForeignKey('item_type', 'item_id')
|
||||
item = GenericForeignKey("item_type", "item_id")
|
||||
|
||||
@classmethod
|
||||
def create_from_dict(cls, d):
|
||||
@@ -339,31 +338,27 @@ class InfoInstance(models.Model):
|
||||
except:
|
||||
raise RuntimeError("invalid parameters supplied supplied")
|
||||
try:
|
||||
return cls.objects.create(
|
||||
rotation=rotation,
|
||||
item=item,
|
||||
duration=duration
|
||||
)
|
||||
return cls.objects.create(rotation=rotation, item=item, duration=duration)
|
||||
except:
|
||||
raise RuntimeError("error while adding instance to db")
|
||||
|
||||
def get_dict(self):
|
||||
"""Convert django model to dict and return it."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'item': self.item.get_dict(),
|
||||
'duration': self.duration,
|
||||
"id": self.id,
|
||||
"item": self.item.get_dict(),
|
||||
"duration": self.duration,
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
"""Return model name."""
|
||||
return "{}: {} ({}s)".format(
|
||||
self.rotation.name, self.item.name, self.duration)
|
||||
return "{}: {} ({}s)".format(self.rotation.name, self.item.name, self.duration)
|
||||
|
||||
|
||||
class Rotation(models.Model):
|
||||
"""Class for rotation model."""
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = models.CharField(max_length=255)
|
||||
|
||||
def get_dict(self):
|
||||
@@ -372,21 +367,20 @@ class Rotation(models.Model):
|
||||
# to avoid excluding items with no expire_date)
|
||||
now = timezone.now()
|
||||
instances = self.instances.all()
|
||||
filtered = filter(lambda i: (i.item.expire_date or now) >= now,
|
||||
list(instances))
|
||||
filtered = filter(lambda i: (i.item.expire_date or now) >= now, list(instances))
|
||||
instance_list = list(map(lambda i: i.get_dict(), filtered))
|
||||
|
||||
return {
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
'instances': instance_list,
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"instances": instance_list,
|
||||
}
|
||||
|
||||
def get_list(self):
|
||||
"""Return list containing infoitem data."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
@@ -397,6 +391,7 @@ class Rotation(models.Model):
|
||||
class ImageUploadForm(forms.Form):
|
||||
"""Form used to handle imageuploads to infoscreen app."""
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = forms.CharField()
|
||||
image = forms.ImageField()
|
||||
|
||||
@@ -404,5 +399,6 @@ class ImageUploadForm(forms.Form):
|
||||
class UploadFileForm(forms.Form):
|
||||
"""Form used for uploading file."""
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = forms.CharField()
|
||||
video = forms.FileField()
|
||||
|
||||
@@ -3,7 +3,7 @@ body {
|
||||
}
|
||||
|
||||
#header:after {
|
||||
content: " ";
|
||||
content: " ";
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ body {
|
||||
.event {
|
||||
font-size: 100px;
|
||||
font-weight: bold;
|
||||
margin-left: 20px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
.event-col{
|
||||
padding-top:1vh;
|
||||
@@ -21,7 +21,7 @@ body {
|
||||
|
||||
.header-row{
|
||||
margin: 30px;
|
||||
margin-left: 20px;
|
||||
margin-left: 20px;
|
||||
font-size: 130px;
|
||||
padding-bottom:20px;
|
||||
color:#24a05f;
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
table {
|
||||
font-size: 4vh;
|
||||
font-family: 'Droid Sans Mono', monospace;
|
||||
}
|
||||
.red {
|
||||
color: red;
|
||||
-webkit-animation-name: blinker;
|
||||
-webkit-animation-duration: 2s;
|
||||
-webkit-animation-timing-function: linear;
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
|
||||
-moz-animation-name: blinker;
|
||||
-moz-animation-duration: 2s;
|
||||
-moz-animation-timing-function: linear;
|
||||
-moz-animation-iteration-count: infinite;
|
||||
|
||||
animation-name: blinker;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: linear;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
.black {
|
||||
color: black;
|
||||
}
|
||||
@-moz-keyframes blinker {
|
||||
0% { opacity: 1.0; }
|
||||
50% { opacity: 0.1; }
|
||||
100% { opacity: 1.0; }
|
||||
}
|
||||
|
||||
@-webkit-keyframes blinker {
|
||||
0% { opacity: 1.0; }
|
||||
50% { opacity: 0.1; }
|
||||
100% { opacity: 1.0; }
|
||||
}
|
||||
|
||||
@keyframes blinker {
|
||||
0% { opacity: 1.0; }
|
||||
50% { opacity: 0.1; }
|
||||
100% { opacity: 1.0; }
|
||||
}
|
||||
thead{
|
||||
background: #f0f0f0;
|
||||
}
|
||||
.header-row{
|
||||
background: #f0f0f0;
|
||||
font-size: 7vh;
|
||||
font-family: 'Droid Sans Mono', monospace;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100vw;
|
||||
padding: 0 0 0 0;
|
||||
}
|
||||
|
||||
.container .table {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.repeat-item.ng-leave {
|
||||
|
||||
}
|
||||
|
||||
.repeat-item.ng-leave.ng-leave-active {
|
||||
opacity: 0;
|
||||
font-size: 0vh;
|
||||
}
|
||||
|
||||
.repeat-item.ng-leave{
|
||||
opacity: 1;
|
||||
font-size: 5vh;
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
#infocontent {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
z-index: -1; /* Ensure div tag stays behind content; -999 might work, too. */
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
#header {
|
||||
height: 30%;
|
||||
width: 100%;
|
||||
background-color:#7c1330;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#header-image {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.article-row {
|
||||
min-height: 20vh;
|
||||
margin: 10px 10px 10px 10px;
|
||||
}
|
||||
|
||||
.article-thumb-col {
|
||||
max-height: 200px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.article-title-col {
|
||||
font-size: 3vw;
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
max-width: 355px;
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
#sossoimage {
|
||||
height: 300px;
|
||||
position: relative;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
.stretch {
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
#post {
|
||||
height: 540px;
|
||||
border:2px solid black;
|
||||
}
|
||||
|
||||
#container {
|
||||
max-height: 70%;
|
||||
}
|
||||
@@ -31,10 +31,10 @@
|
||||
max-height 200px;
|
||||
}
|
||||
|
||||
#sossoimage {
|
||||
#sossoimage {
|
||||
height:300px;
|
||||
position: relative;
|
||||
left: 0px;
|
||||
position: relative;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<link rel="stylesheet" href="/static/infoscreen/css/coffee.css">
|
||||
<iframe src="https://host2.kilta.aalto.fi/kahvi/cups" allowfullscreen=true sandbox="allow-scripts allow-pointer-lock allow-same-origin">
|
||||
<p>Your browser does not support iframes.</p>
|
||||
</iframe>
|
||||
@@ -1,5 +1,5 @@
|
||||
<link rel="stylesheet" href="/static/infoscreen/css/events.css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Droid+Sans+Mono" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css?family=Droid+Sans+Mono" rel="stylesheet">
|
||||
<div class="container" ng-app="myApp" ng-controller="EventController">
|
||||
<div class="header-row row">
|
||||
<div class="col-sm-6">Tapahtuma</div>
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
<link rel="stylesheet" href="/static/infoscreen/css/hsl.css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Droid+Sans+Mono" rel="stylesheet">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.19.1/locale/fi.js"></script>
|
||||
<div class="container" ng-app="myApp" ng-controller="timetableCtrl">
|
||||
<div class="header-row row">
|
||||
<div class="col-sm-2"><p>{{clock | date:'HH:mm'}}</p></div>
|
||||
<div class="col-sm-8">HSL-Aikataulut</div>
|
||||
<div class="col-sm-2 time"></div>
|
||||
</div>
|
||||
<h1 style="font-size: 10vh; text-align: center" ng-if="error">
|
||||
{{error}}
|
||||
</h1>
|
||||
<table ng-if="!error" class="table table-striped row">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Aika
|
||||
</th>
|
||||
<th>
|
||||
Linja
|
||||
</th>
|
||||
<th>
|
||||
Pysäkki
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="repeat-item" ng-repeat="x in stoptimes | orderBy: ['utc'] | limitTo: 11">
|
||||
<td style="min-width: 300px">
|
||||
{{x.timestamp}}
|
||||
</td>
|
||||
<td>
|
||||
<strong>{{x.route}}</strong>, {{x.headsign}}
|
||||
</td>
|
||||
<td>
|
||||
{{x.stop}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="/static/infoscreen/css/lunch.css">
|
||||
<div ng-controller="LunchController">
|
||||
<div id="container">
|
||||
<div class="restaurant row" ng-repeat="restaurant in data">
|
||||
<div class="lunch-option" ng-repeat="l in lunch">
|
||||
<h3 ng-bind-html="l.title | unsafe"></h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,10 +1,10 @@
|
||||
<div ng-controller="infoadmin_hslitem_create" style="margin-top:20px;">
|
||||
<div>
|
||||
Create new item to show hsl ttimetables. Name is used only as identifier
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Name:</label>
|
||||
<input type="text" class="form-control" ng-model="item.name"></input>
|
||||
</div>
|
||||
<input type="button" class="btn btn-success" ng-click="send()" value="create"></input>
|
||||
</div>
|
||||
<div ng-controller="infoadmin_lunchitem_create" style="margin-top:20px;">
|
||||
<div>
|
||||
Create new item to show restaurants. Name is used only as identifier
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Name:</label>
|
||||
<input type="text" class="form-control" ng-model="item.name"></input>
|
||||
</div>
|
||||
<input type="button" class="btn btn-success" ng-click="send()" value="create"></input>
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
<h1>testi2</h1>
|
||||
@@ -1 +0,0 @@
|
||||
<h1>testi3</h1>
|
||||
|
Before Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 736 KiB |
|
Before Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 96 KiB |
@@ -182,8 +182,8 @@ var simple_controllers = [
|
||||
"external_image",
|
||||
"abbitem",
|
||||
"sossoitem",
|
||||
"lunchitem",
|
||||
"eventitem",
|
||||
"hslitem",
|
||||
"websiteitem",
|
||||
"apyitem",
|
||||
];
|
||||
|
||||
@@ -56,7 +56,8 @@ app.filter('unsafe', function($sce) {
|
||||
app.controller('ABBController', function($scope, $http){
|
||||
$scope.jobs = [];
|
||||
var min_date = moment().subtract(30,'days').format("YYYY-MM-DD%20HH:mm:ss");
|
||||
var url = "https://sahkoinsinoorikilta.fi/api/news.php";
|
||||
// TODO: FIX, we try to get rid of php, not depend on it!
|
||||
var url = "https://old.sahkoinsinoorikilta.fi/api/news.php";
|
||||
var params = "?type=11&lang=fi&title_search=ABB&min_date="+min_date
|
||||
$http.get(url+params).then(function(response){
|
||||
$scope.jobs = _.filter(response.data, function(job){
|
||||
@@ -82,6 +83,21 @@ app.controller('SossoController', function($scope, $http) {
|
||||
})
|
||||
});
|
||||
|
||||
app.controller('LunchController', function ($scope, $http) {
|
||||
$scope.data = [];
|
||||
var restaurants = [42];
|
||||
var restaurant_names = ["TUAS"]
|
||||
var cur_date = new Date().toISOString().split("T")[0]
|
||||
$http.get("https://kitchen.kanttiinit.fi/menus?restaurants=" + restaurants.join(",") + "&days=" + cur_date).then(function (response) {
|
||||
$scope.data = restaurant_names.map(function(n, idx) {
|
||||
return {
|
||||
name: n,
|
||||
lunch: response[idx][cur_date],
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
app.controller('ApyController', function($scope, $http) {
|
||||
$scope.items = [];
|
||||
$http.get("/infoscreen/apyjson").then(function(response)
|
||||
@@ -119,35 +135,3 @@ app.filter('unixTimeToDifference', function() {
|
||||
return res;
|
||||
}
|
||||
})
|
||||
|
||||
app.controller('timetableCtrl',
|
||||
function($scope, $http, $interval) {
|
||||
function load() {
|
||||
$http.get('/infoscreen/hsl_data')
|
||||
.then(function(data, status, headers, config) { //eslint-disable-line no-unused-vars
|
||||
$scope.stoptimes = data.data;
|
||||
$scope.error = data.data.error || null;
|
||||
});
|
||||
$http.get('/infoscreen/hsl_data/settings')
|
||||
.then(function(data, status, headers, config) { //eslint-disable-line no-unused-vars
|
||||
$scope.departureThreshold = data.data['departure_threshold'];
|
||||
$scope.hurryThreshold = data.data['hurry_threshold'];
|
||||
});
|
||||
}
|
||||
|
||||
function update_clock() {
|
||||
$scope.clock = Date.now();
|
||||
}
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
$interval.cancel(load_interval);
|
||||
$interval.cancel(clock_interval);
|
||||
});
|
||||
|
||||
var load_interval = $interval(load, 5000);
|
||||
var clock_interval = $interval(update_clock, 1000);
|
||||
|
||||
update_clock();
|
||||
load();
|
||||
}
|
||||
);
|
||||
|
||||
@@ -35,6 +35,6 @@ class InfoscreenTestCase(TestCase):
|
||||
That would mean that something meaningful has been included
|
||||
in the response.
|
||||
"""
|
||||
resp = self.c.get('/infoscreen/items')
|
||||
resp = self.c.get("/infoscreen/items")
|
||||
content = resp.json()
|
||||
self.assertTrue(len(content) > 0)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""File containing infoscreen urls."""
|
||||
|
||||
from django.conf.urls import url
|
||||
from django.urls import re_path
|
||||
from django.conf import settings
|
||||
|
||||
from infoscreen.views import index
|
||||
@@ -18,43 +18,40 @@ from infoscreen.views import create_image_item
|
||||
from infoscreen.views import create_video_item
|
||||
from infoscreen.views import createABBItem
|
||||
from infoscreen.views import createSossoItem
|
||||
from infoscreen.views import createHslItem
|
||||
from infoscreen.views import createLunchItem
|
||||
from infoscreen.views import createEventItem
|
||||
from infoscreen.views import createExternalWebsiteItem
|
||||
from infoscreen.views import create_rotation
|
||||
from infoscreen.views import delete_rotation
|
||||
from infoscreen.views import CurrentHSLView
|
||||
from infoscreen.views import createApyItem
|
||||
from infoscreen.views import hsl_timetable_settings
|
||||
from infoscreen.views import get_apy_json
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', default),
|
||||
url(r'^admin$', admin),
|
||||
url(r'^(?P<idx>\d+)$', index),
|
||||
url(r'^items$', info_items),
|
||||
url(r'^rotation/(?P<idx>\d+)$', rotation),
|
||||
url(r'^rotations$', rotations),
|
||||
url(r'^instance$', createInstance),
|
||||
url(r'^instance/(?P<idx>\d+)$', deleteInstance),
|
||||
url(r'^types$', info_types),
|
||||
url(r'^delete_item/(?P<type_id>\d+)/(?P<idx>\d+)$', delete_info_item),
|
||||
url(r'^create_external_image$', createExternalImageInfoItem),
|
||||
url(r'^create_image$', create_image_item),
|
||||
url(r'^create_video$', create_video_item),
|
||||
url(r'^create_abbitem$', createABBItem),
|
||||
url(r'^create_sossoitem$', createSossoItem),
|
||||
url(r'^create_eventitem$', createEventItem),
|
||||
url(r'^create_hslitem$', createHslItem),
|
||||
url(r'^create_apyitem$', createApyItem),
|
||||
url(r'^create_websiteitem$', createExternalWebsiteItem),
|
||||
url(r'^create_rotation$', create_rotation),
|
||||
url(r'^delete_rotation/(?P<id>\d+)$', delete_rotation),
|
||||
url(r'^hsl_data$', CurrentHSLView),
|
||||
url(r'^hsl_data/settings$', hsl_timetable_settings),
|
||||
url(r'^apyjson', get_apy_json),
|
||||
re_path(r"^$", default),
|
||||
re_path(r"^admin$", admin),
|
||||
re_path(r"^(?P<idx>\d+)$", index),
|
||||
re_path(r"^items$", info_items),
|
||||
re_path(r"^rotation/(?P<idx>\d+)$", rotation),
|
||||
re_path(r"^rotations$", rotations),
|
||||
re_path(r"^instance$", createInstance),
|
||||
re_path(r"^instance/(?P<idx>\d+)$", deleteInstance),
|
||||
re_path(r"^types$", info_types),
|
||||
re_path(r"^delete_item/(?P<type_id>\d+)/(?P<idx>\d+)$", delete_info_item),
|
||||
re_path(r"^create_external_image$", createExternalImageInfoItem),
|
||||
re_path(r"^create_image$", create_image_item),
|
||||
re_path(r"^create_video$", create_video_item),
|
||||
re_path(r"^create_abbitem$", createABBItem),
|
||||
re_path(r"^create_sossoitem$", createSossoItem),
|
||||
re_path(r"^create_lunchitem$", createLunchItem),
|
||||
re_path(r"^create_eventitem$", createEventItem),
|
||||
re_path(r"^create_apyitem$", createApyItem),
|
||||
re_path(r"^create_websiteitem$", createExternalWebsiteItem),
|
||||
re_path(r"^create_rotation$", create_rotation),
|
||||
re_path(r"^delete_rotation/(?P<id>\d+)$", delete_rotation),
|
||||
re_path(r"^apyjson", get_apy_json),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
|
||||
urlpatterns += staticfiles_urlpatterns()
|
||||
|
||||
@@ -16,16 +16,27 @@ import threading
|
||||
import requests
|
||||
|
||||
from infoscreen.models import (
|
||||
Rotation, InfoItem, InfoInstance, ABBInfoItem, ExternalImageInfoItem,
|
||||
ImageInfoItem, SossoInfoItem, HslInfoItem, EventInfoItem,
|
||||
ExternalWebsiteInfoItem, ImageUploadForm, ApyInfoItem, VideoInfoItem)
|
||||
Rotation,
|
||||
InfoItem,
|
||||
InfoInstance,
|
||||
ABBInfoItem,
|
||||
ExternalImageInfoItem,
|
||||
ImageInfoItem,
|
||||
SossoInfoItem,
|
||||
LunchItem,
|
||||
EventInfoItem,
|
||||
ExternalWebsiteInfoItem,
|
||||
ImageUploadForm,
|
||||
ApyInfoItem,
|
||||
VideoInfoItem,
|
||||
)
|
||||
|
||||
|
||||
@login_required(login_url='/admin/login')
|
||||
@permission_required('infoscreen.change_infoinstance', raise_exception=True)
|
||||
@login_required(login_url="/admin/login")
|
||||
@permission_required("infoscreen.change_infoinstance", raise_exception=True)
|
||||
def admin(request, *args, **kwargs):
|
||||
"""Render infoscreen admin page."""
|
||||
return render(request, 'infoscreen:infoscreen_admin.html', {})
|
||||
return render(request, "infoscreen/infoscreen_admin.html", {})
|
||||
|
||||
|
||||
def create_item_generator(model):
|
||||
@@ -33,20 +44,23 @@ def create_item_generator(model):
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@require_http_methods(["POST"])
|
||||
@login_required(login_url='/admin/login')
|
||||
@permission_required('infoscreen.add_infoinstance', raise_exception=True)
|
||||
@login_required(login_url="/admin/login")
|
||||
@permission_required("infoscreen.add_infoinstance", raise_exception=True)
|
||||
def create_item(request, *args, **kwargs):
|
||||
try:
|
||||
data = json.loads(request.body.decode("utf-8"))
|
||||
except json.JSONDecodeError:
|
||||
return HttpResponseBadRequest(
|
||||
'{"status":"failure","error":"invalid json supplied"}')
|
||||
'{"status":"failure","error":"invalid json supplied"}'
|
||||
)
|
||||
try:
|
||||
model.create_from_dict(data)
|
||||
return HttpResponse('{"status":"success"}')
|
||||
except RuntimeError as e:
|
||||
return HttpResponseBadRequest(
|
||||
json.dumps({"status": "failure", "error": str(e)}))
|
||||
json.dumps({"status": "failure", "error": str(e)})
|
||||
)
|
||||
|
||||
return create_item
|
||||
|
||||
|
||||
@@ -55,8 +69,8 @@ def delete_item_generator(model):
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@require_http_methods(["DELETE"])
|
||||
@login_required(login_url='/admin/login')
|
||||
@permission_required('infoscreen.delete_infoinstance', raise_exception=True)
|
||||
@login_required(login_url="/admin/login")
|
||||
@permission_required("infoscreen.delete_infoinstance", raise_exception=True)
|
||||
def delete_item(request, *args, **kwargs):
|
||||
idx = kwargs.pop("idx", 0)
|
||||
try:
|
||||
@@ -72,13 +86,14 @@ def delete_item_generator(model):
|
||||
resp = HttpResponse('{"error" : "could not delete item"}')
|
||||
resp.status_code = 500
|
||||
return resp
|
||||
|
||||
return delete_item
|
||||
|
||||
|
||||
# due to model structure this is little complicated
|
||||
@ensure_csrf_cookie
|
||||
@login_required(login_url='/admin/login')
|
||||
@permission_required('infoscreen.delete_infoinstance', raise_exception=True)
|
||||
@login_required(login_url="/admin/login")
|
||||
@permission_required("infoscreen.delete_infoinstance", raise_exception=True)
|
||||
@require_http_methods(["DELETE"])
|
||||
def delete_info_item(request, *args, **kwargs):
|
||||
"""Delete info item."""
|
||||
@@ -102,42 +117,44 @@ def delete_info_item(request, *args, **kwargs):
|
||||
|
||||
@require_http_methods(["POST"])
|
||||
@ensure_csrf_cookie
|
||||
@login_required(login_url='/admin/login')
|
||||
@permission_required('infoscreen.add_infoinstance', raise_exception=True)
|
||||
@login_required(login_url="/admin/login")
|
||||
@permission_required("infoscreen.add_infoinstance", raise_exception=True)
|
||||
def create_image_item(request, *args, **kwargs):
|
||||
"""Create image Infoscreen item."""
|
||||
form = ImageUploadForm(request.POST, request.FILES)
|
||||
if not form.is_valid():
|
||||
return HttpResponseBadRequest('{"status": "failure",'
|
||||
'"error": "invalid data supplied"}')
|
||||
return HttpResponseBadRequest(
|
||||
'{"status": "failure",' '"error": "invalid data supplied"}'
|
||||
)
|
||||
|
||||
img = form.cleaned_data['image']
|
||||
name = form.cleaned_data['name']
|
||||
img = form.cleaned_data["image"]
|
||||
name = form.cleaned_data["name"]
|
||||
ImageInfoItem.objects.create(img=img, name=name)
|
||||
return HttpResponse('{"status":"success"}')
|
||||
|
||||
|
||||
@require_http_methods(["POST"])
|
||||
@ensure_csrf_cookie
|
||||
@login_required(login_url='/admin/login')
|
||||
@permission_required('infoscreen.add_infoinstance', raise_exception=True)
|
||||
@login_required(login_url="/admin/login")
|
||||
@permission_required("infoscreen.add_infoinstance", raise_exception=True)
|
||||
def create_video_item(request, *args, **kwargs):
|
||||
"""Create video Infoscreen item."""
|
||||
form = UploadFileForm(request.POST, request.FILES)
|
||||
if not form.is_valid():
|
||||
return HttpResponseBadRequest('{"status": "failure",'
|
||||
'"error": "invalid data supplied"}')
|
||||
return HttpResponseBadRequest(
|
||||
'{"status": "failure",' '"error": "invalid data supplied"}'
|
||||
)
|
||||
|
||||
video = form.cleaned_data['video']
|
||||
name = form.cleaned_data['name']
|
||||
video = form.cleaned_data["video"]
|
||||
name = form.cleaned_data["name"]
|
||||
VideoInfoItem.objects.create(video=video, name=name)
|
||||
return HttpResponse('{"status": "success"}')
|
||||
|
||||
|
||||
@require_http_methods(["POST"])
|
||||
@ensure_csrf_cookie
|
||||
@login_required(login_url='/admin/login')
|
||||
@permission_required('infoscreen.add_rotation', raise_exception=True)
|
||||
@login_required(login_url="/admin/login")
|
||||
@permission_required("infoscreen.add_rotation", raise_exception=True)
|
||||
def create_rotation(request, *args, **kwargs):
|
||||
"""Create rotation."""
|
||||
try:
|
||||
@@ -150,16 +167,15 @@ def create_rotation(request, *args, **kwargs):
|
||||
Rotation.objects.create(name=name)
|
||||
resp = HttpResponse(status=200)
|
||||
except DatabaseError:
|
||||
resp = HttpResponse(
|
||||
'{"error" : "could not create rotation!"}', status=400)
|
||||
resp = HttpResponse('{"error" : "could not create rotation!"}', status=400)
|
||||
|
||||
return resp
|
||||
|
||||
|
||||
@require_http_methods(["DELETE"])
|
||||
@ensure_csrf_cookie
|
||||
@login_required(login_url='/admin/login')
|
||||
@permission_required('infoscreen.delete_rotation', raise_exception=True)
|
||||
@login_required(login_url="/admin/login")
|
||||
@permission_required("infoscreen.delete_rotation", raise_exception=True)
|
||||
def delete_rotation(request, *args, **kwargs):
|
||||
"""Delete rotation."""
|
||||
id = kwargs.pop("id", 0)
|
||||
@@ -169,8 +185,7 @@ def delete_rotation(request, *args, **kwargs):
|
||||
Rotation.objects.filter(id=id).delete()
|
||||
resp = HttpResponse(status=200)
|
||||
except DatabaseError:
|
||||
resp = HttpResponse(
|
||||
'{"error" : "could not delete rotation!"}', status=400)
|
||||
resp = HttpResponse('{"error" : "could not delete rotation!"}', status=400)
|
||||
|
||||
return resp
|
||||
|
||||
@@ -179,7 +194,7 @@ createInstance = create_item_generator(InfoInstance)
|
||||
deleteInstance = delete_item_generator(InfoInstance)
|
||||
createABBItem = create_item_generator(ABBInfoItem)
|
||||
createSossoItem = create_item_generator(SossoInfoItem)
|
||||
createHslItem = create_item_generator(HslInfoItem)
|
||||
createLunchItem = create_item_generator(LunchItem)
|
||||
createExternalImageInfoItem = create_item_generator(ExternalImageInfoItem)
|
||||
createExternalWebsiteItem = create_item_generator(ExternalWebsiteInfoItem)
|
||||
createEventItem = create_item_generator(EventInfoItem)
|
||||
|
||||
@@ -5,7 +5,6 @@ from django.conf import settings
|
||||
from django.db import DatabaseError
|
||||
|
||||
from infoscreen.models import Rotation, InfoItem, InfoInstance
|
||||
from infoscreen.hsl_fetcher import fetch as hsl_fetch
|
||||
|
||||
import json
|
||||
import logging
|
||||
@@ -16,7 +15,7 @@ import requests
|
||||
@require_http_methods(["GET"])
|
||||
def index(request, idx, *args, **kwargs):
|
||||
"""Render infoscreen index page."""
|
||||
return render(request, 'infoscreen_index.html', {'rotation': idx})
|
||||
return render(request, "infoscreen/infoscreen_index.html", {"rotation": idx})
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
@@ -33,7 +32,8 @@ def default(request, *args, **kwargs):
|
||||
def get_apy_json(request):
|
||||
"""Render APY diilikone page."""
|
||||
return HttpResponse(
|
||||
requests.get("https://api-diilikone.apy.fi/deals/top-groups").text)
|
||||
requests.get("https://api-diilikone.apy.fi/deals/top-groups").text
|
||||
)
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
@@ -62,10 +62,12 @@ def info_types(request, *args, **kwargs):
|
||||
types = []
|
||||
classes = InfoItem.get_subclasses()
|
||||
for c in classes:
|
||||
types.append({
|
||||
"name": c.display_name,
|
||||
"create_template_url": c.get_create_template_url(),
|
||||
})
|
||||
types.append(
|
||||
{
|
||||
"name": c.display_name,
|
||||
"create_template_url": c.get_create_template_url(),
|
||||
}
|
||||
)
|
||||
return HttpResponse(json.dumps(types))
|
||||
|
||||
|
||||
@@ -79,25 +81,3 @@ def info_items(request, *args, **kwargs):
|
||||
items.append(i.get_dict())
|
||||
|
||||
return JsonResponse(items, safe=False)
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
def hsl_timetable_settings(request, *args, **kwargs):
|
||||
"""Set HSL timetable settings."""
|
||||
d = {"departure_threshold": settings.HSL_DEPARTURE_THRESHOLD,
|
||||
"hurry_threshold": settings.HSL_HURRY_THRESHOLD}
|
||||
|
||||
return JsonResponse(d, status=200)
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
def CurrentHSLView(request, *args, **kwargs):
|
||||
"""Get HSL data and return it."""
|
||||
try:
|
||||
api_resp = hsl_fetch()
|
||||
except Exception as ex:
|
||||
logging.exception('Failed to fetch HSL timetables.')
|
||||
error = {'error': 'Aikataulujen haku epäonnistui.'}
|
||||
return JsonResponse(error, status=200)
|
||||
|
||||
return JsonResponse(api_resp, status=200, safe=False)
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
from django.contrib import admin
|
||||
from modeltranslation.admin import TranslationAdmin
|
||||
|
||||
from kaehmy.models import Application, Comment, CustomRole, PresetRole, TelegramChannel
|
||||
from kaehmy.models import Application, Comment, CustomRole, PresetRole
|
||||
|
||||
admin.site.register(Application)
|
||||
admin.site.register(Comment)
|
||||
admin.site.register(CustomRole)
|
||||
admin.site.register(PresetRole, TranslationAdmin)
|
||||
admin.site.register(TelegramChannel)
|
||||
|
||||
@@ -2,4 +2,4 @@ from django.apps import AppConfig
|
||||
|
||||
|
||||
class KaehmyConfig(AppConfig):
|
||||
name = 'kaehmy'
|
||||
name = "kaehmy"
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from kaehmy.models import PresetRole, CustomRole, Application, Comment, KaehmyBaseRole
|
||||
from kaehmy.models import PresetRole, CustomRole, Application, Comment, BaseRole
|
||||
|
||||
|
||||
class CheckboxSelectMultiple(forms.widgets.CheckboxSelectMultiple):
|
||||
option_template_name = 'checkbox_option.html'
|
||||
option_template_name = "checkbox_option.html"
|
||||
|
||||
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
|
||||
dic = super(CheckboxSelectMultiple, self).create_option(name, value, label, selected, index, subindex, attrs)
|
||||
description = PresetRole.objects.get(id=value).description
|
||||
dic['description'] = description
|
||||
def create_option(
|
||||
self, name, formIterator, label, selected, index, subindex=None, attrs=None
|
||||
):
|
||||
dic = super(CheckboxSelectMultiple, self).create_option(
|
||||
name, formIterator, label, selected, index, subindex, attrs
|
||||
)
|
||||
description = PresetRole.objects.get(id=formIterator.value).description
|
||||
dic["description"] = description
|
||||
return dic
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -25,30 +29,46 @@ class ApplicationForm(forms.ModelForm):
|
||||
"""Meta for class Application."""
|
||||
|
||||
model = Application
|
||||
fields = ['name', 'email', 'phone_number', 'year',
|
||||
'preset_roles', 'custom_roles', 'custom_role_name',
|
||||
'custom_role_is_board', 'text']
|
||||
fields = [
|
||||
"name",
|
||||
"email",
|
||||
"phone_number",
|
||||
"year",
|
||||
"preset_roles",
|
||||
"custom_roles",
|
||||
"custom_role_name",
|
||||
"custom_role_is_board",
|
||||
"text",
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ApplicationForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.fields["email"].label = _('Email (not public)')
|
||||
self.fields["phone_number"].label = _('Phone number (not public)')
|
||||
self.fields["email"].label = _("Email (not public)")
|
||||
self.fields["phone_number"].label = _("Phone number (not public)")
|
||||
|
||||
custom_roles_exist = CustomRole.objects.all().exists()
|
||||
self.fields["custom_roles"].widget = forms.widgets.CheckboxSelectMultiple() if custom_roles_exist else forms.HiddenInput()
|
||||
self.fields["custom_roles"].widget = (
|
||||
forms.widgets.CheckboxSelectMultiple()
|
||||
if custom_roles_exist
|
||||
else forms.HiddenInput()
|
||||
)
|
||||
self.fields["custom_roles"].help_text = ""
|
||||
self.fields["custom_roles"].label = _('Custom roles')
|
||||
self.fields["custom_roles"].label = _("Custom roles")
|
||||
self.fields["custom_roles"].queryset = CustomRole.objects.all()
|
||||
|
||||
for cat_id, category in KaehmyBaseRole.CATEGORIES:
|
||||
key = 'preset_roles_{}'.format(cat_id)
|
||||
qset = PresetRole.objects.filter(category=cat_id).order_by('category', '-is_board')
|
||||
for cat_id, category in BaseRole.CATEGORIES:
|
||||
key = "preset_roles_{}".format(cat_id)
|
||||
qset = PresetRole.objects.filter(category=cat_id).order_by(
|
||||
"category", "-is_board"
|
||||
)
|
||||
self.fields[key] = forms.ModelMultipleChoiceField(qset)
|
||||
self.fields[key].widget = CheckboxSelectMultiple(attrs={
|
||||
'title': _('Preset roles'),
|
||||
'name': 'preset_roles',
|
||||
})
|
||||
self.fields[key].widget = CheckboxSelectMultiple(
|
||||
attrs={
|
||||
"title": _("Preset roles"),
|
||||
"name": "preset_roles",
|
||||
}
|
||||
)
|
||||
self.fields[key].help_text = ""
|
||||
self.fields[key].queryset = qset
|
||||
self.fields[key].label = _(category)
|
||||
@@ -57,33 +77,38 @@ class ApplicationForm(forms.ModelForm):
|
||||
def clean(self):
|
||||
cleaned_data = super(ApplicationForm, self).clean()
|
||||
for key in cleaned_data.keys():
|
||||
if 'preset_roles_' in key:
|
||||
cleaned_data['preset_roles'] = cleaned_data['preset_roles'] | cleaned_data[key]
|
||||
if "preset_roles_" in key:
|
||||
cleaned_data["preset_roles"] = (
|
||||
cleaned_data["preset_roles"] | cleaned_data[key]
|
||||
)
|
||||
|
||||
return cleaned_data
|
||||
|
||||
def clean_phone_number(self):
|
||||
"""Clean phone number field."""
|
||||
number = self.cleaned_data.get('phone_number')
|
||||
number = self.cleaned_data.get("phone_number")
|
||||
if number.isdigit():
|
||||
return number
|
||||
else:
|
||||
raise ValidationError(_('Invalid phone number'))
|
||||
raise ValidationError(_("Invalid phone number"))
|
||||
|
||||
def clean_custom_role_name(self):
|
||||
"""Check that no other custom role with same name exists."""
|
||||
custom_name = self.cleaned_data.get('custom_role_name')
|
||||
custom_name = self.cleaned_data.get("custom_role_name")
|
||||
if not CustomRole.objects.filter(name=custom_name).exists():
|
||||
return custom_name
|
||||
else:
|
||||
raise ValidationError(_('Custom role with the same name already exists.'))
|
||||
raise ValidationError(_("Custom role with the same name already exists."))
|
||||
|
||||
def non_role_fields(self):
|
||||
return [self.fields[k] for k in self.fields.keys() if k not in ["preset_roles", "custom_roles"]]
|
||||
return [
|
||||
self.fields[k]
|
||||
for k in self.fields.keys()
|
||||
if k not in ["preset_roles", "custom_roles"]
|
||||
]
|
||||
|
||||
|
||||
class CommentForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Comment
|
||||
fields = ['name', 'email', 'message', 'parent']
|
||||
fields = ["name", "email", "message", "parent"]
|
||||
|
||||
@@ -12,82 +12,188 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('webapp', '0037_auto_20180125_2131'),
|
||||
("webapp", "0037_auto_20180125_2131"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CommentParent',
|
||||
name="CommentParent",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(default='', max_length=255, verbose_name='Name')),
|
||||
('email', models.EmailField(default='', max_length=254, verbose_name='Email')),
|
||||
('timestamp', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Timestamp')),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"name",
|
||||
models.CharField(default="", max_length=255, verbose_name="Name"),
|
||||
),
|
||||
(
|
||||
"email",
|
||||
models.EmailField(default="", max_length=254, verbose_name="Email"),
|
||||
),
|
||||
(
|
||||
"timestamp",
|
||||
models.DateTimeField(
|
||||
default=django.utils.timezone.now, verbose_name="Timestamp"
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CustomRole',
|
||||
name="CustomRole",
|
||||
fields=[
|
||||
('baserole_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='webapp.BaseRole')),
|
||||
(
|
||||
"baserole_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="webapp.BaseRole",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Custom kaehmy roles',
|
||||
'verbose_name': 'Custom kaehmy role',
|
||||
"verbose_name_plural": "Custom kaehmy roles",
|
||||
"verbose_name": "Custom kaehmy role",
|
||||
},
|
||||
bases=('webapp.baserole',),
|
||||
bases=("webapp.baserole",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PresetRole',
|
||||
name="PresetRole",
|
||||
fields=[
|
||||
('presetrole_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='webapp.PresetRole')),
|
||||
(
|
||||
"presetrole_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="webapp.PresetRole",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Preset kaehmy roles',
|
||||
'verbose_name': 'Preset kaehmy role',
|
||||
"verbose_name_plural": "Preset kaehmy roles",
|
||||
"verbose_name": "Preset kaehmy role",
|
||||
},
|
||||
bases=('webapp.presetrole',),
|
||||
bases=("webapp.presetrole",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TelegramChannel',
|
||||
name="TelegramChannel",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('channel_id', models.CharField(max_length=255, unique=True)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=255)),
|
||||
("channel_id", models.CharField(max_length=255, unique=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Telegram channels',
|
||||
'verbose_name': 'Telegram channel',
|
||||
"verbose_name_plural": "Telegram channels",
|
||||
"verbose_name": "Telegram channel",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Application',
|
||||
name="Application",
|
||||
fields=[
|
||||
('commentparent_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='kaehmy.CommentParent')),
|
||||
('phone_number', models.CharField(default='', max_length=10, verbose_name='Phone number')),
|
||||
('year', models.IntegerField(choices=[(1, '1'), (2, '2'), (3, '3'), (4, '4'), (5, 'N')], verbose_name='Year')),
|
||||
('text', models.TextField(default='', max_length=300, verbose_name='Text')),
|
||||
('custom_role_name', models.CharField(blank=True, max_length=255, verbose_name='Custom role name')),
|
||||
('custom_role_is_board', models.BooleanField(verbose_name='Board member')),
|
||||
('custom_roles', models.ManyToManyField(blank=True, related_name='forms', to='kaehmy.CustomRole')),
|
||||
('preset_roles', models.ManyToManyField(blank=True, related_name='forms', to='kaehmy.PresetRole')),
|
||||
(
|
||||
"commentparent_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="kaehmy.CommentParent",
|
||||
),
|
||||
),
|
||||
(
|
||||
"phone_number",
|
||||
models.CharField(
|
||||
default="", max_length=10, verbose_name="Phone number"
|
||||
),
|
||||
),
|
||||
(
|
||||
"year",
|
||||
models.IntegerField(
|
||||
choices=[(1, "1"), (2, "2"), (3, "3"), (4, "4"), (5, "N")],
|
||||
verbose_name="Year",
|
||||
),
|
||||
),
|
||||
(
|
||||
"text",
|
||||
models.TextField(default="", max_length=300, verbose_name="Text"),
|
||||
),
|
||||
(
|
||||
"custom_role_name",
|
||||
models.CharField(
|
||||
blank=True, max_length=255, verbose_name="Custom role name"
|
||||
),
|
||||
),
|
||||
(
|
||||
"custom_role_is_board",
|
||||
models.BooleanField(verbose_name="Board member"),
|
||||
),
|
||||
(
|
||||
"custom_roles",
|
||||
models.ManyToManyField(
|
||||
blank=True, related_name="forms", to="kaehmy.CustomRole"
|
||||
),
|
||||
),
|
||||
(
|
||||
"preset_roles",
|
||||
models.ManyToManyField(
|
||||
blank=True, related_name="forms", to="kaehmy.PresetRole"
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Kaehmylomakkeet',
|
||||
'verbose_name': 'Kaehmylomake',
|
||||
"verbose_name_plural": "Kaehmylomakkeet",
|
||||
"verbose_name": "Kaehmylomake",
|
||||
},
|
||||
bases=('kaehmy.commentparent',),
|
||||
bases=("kaehmy.commentparent",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Comment',
|
||||
name="Comment",
|
||||
fields=[
|
||||
('commentparent_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='kaehmy.CommentParent')),
|
||||
('message', models.TextField(verbose_name='Message')),
|
||||
('parent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='kaehmy.CommentParent')),
|
||||
(
|
||||
"commentparent_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="kaehmy.CommentParent",
|
||||
),
|
||||
),
|
||||
("message", models.TextField(verbose_name="Message")),
|
||||
(
|
||||
"parent",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="messages",
|
||||
to="kaehmy.CommentParent",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Kaehmykommentit',
|
||||
'verbose_name': 'Kaehmykommentti',
|
||||
"verbose_name_plural": "Kaehmykommentit",
|
||||
"verbose_name": "Kaehmykommentti",
|
||||
},
|
||||
bases=('kaehmy.commentparent',),
|
||||
bases=("kaehmy.commentparent",),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -7,26 +7,59 @@ import django.db.models.deletion
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('webapp', '0047_auto_20180710_2110'),
|
||||
('kaehmy', '0001_initial'),
|
||||
("webapp", "0047_auto_20180710_2110"),
|
||||
("kaehmy", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='KaehmyBaseRole',
|
||||
name="KaehmyBaseRole",
|
||||
fields=[
|
||||
('baserole_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='webapp.BaseRole')),
|
||||
('category', models.CharField(choices=[('corporate', 'Corporate affairs'), ('freshman', 'Freshmen'), ('international', 'International'), ('external', 'External affairs'), ('media', 'Media'), ('tech', 'Technology'), ('wellbeing', 'Wellbeing'), ('elepaja', 'Elepaja'), ('ceremonies', 'Ceremonies'), ('culture', 'Culture'), ('studies', 'Studies'), ('sosso', 'Sössö magazine'), ('alumni', 'Alumni relations'), ('others', 'Others')], default='others', max_length=255, verbose_name='Category')),
|
||||
(
|
||||
"baserole_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="webapp.BaseRole",
|
||||
),
|
||||
),
|
||||
(
|
||||
"category",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("corporate", "Corporate affairs"),
|
||||
("freshman", "Freshmen"),
|
||||
("international", "International"),
|
||||
("external", "External affairs"),
|
||||
("media", "Media"),
|
||||
("tech", "Technology"),
|
||||
("wellbeing", "Wellbeing"),
|
||||
("elepaja", "Elepaja"),
|
||||
("ceremonies", "Ceremonies"),
|
||||
("culture", "Culture"),
|
||||
("studies", "Studies"),
|
||||
("sosso", "Sössö magazine"),
|
||||
("alumni", "Alumni relations"),
|
||||
("others", "Others"),
|
||||
],
|
||||
default="others",
|
||||
max_length=255,
|
||||
verbose_name="Category",
|
||||
),
|
||||
),
|
||||
],
|
||||
bases=('webapp.baserole',),
|
||||
bases=("webapp.baserole",),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Application',
|
||||
name="Application",
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='customrole',
|
||||
name="customrole",
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='presetrole',
|
||||
name="presetrole",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -7,57 +7,113 @@ import django.db.models.deletion
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('kaehmy', '0002_auto_20180902_1929'),
|
||||
("kaehmy", "0002_auto_20180902_1929"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Application',
|
||||
name="Application",
|
||||
fields=[
|
||||
('commentparent_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='kaehmy.CommentParent')),
|
||||
('phone_number', models.CharField(default='', max_length=10, verbose_name='Phone number')),
|
||||
('year', models.IntegerField(choices=[(1, '1'), (2, '2'), (3, '3'), (4, '4'), (5, 'N')], verbose_name='Year')),
|
||||
('text', models.TextField(default='', max_length=300, verbose_name='Text')),
|
||||
('custom_role_name', models.CharField(blank=True, max_length=255, verbose_name='Custom role name')),
|
||||
('custom_role_is_board', models.BooleanField(verbose_name='Board member')),
|
||||
(
|
||||
"commentparent_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="kaehmy.CommentParent",
|
||||
),
|
||||
),
|
||||
(
|
||||
"phone_number",
|
||||
models.CharField(
|
||||
default="", max_length=10, verbose_name="Phone number"
|
||||
),
|
||||
),
|
||||
(
|
||||
"year",
|
||||
models.IntegerField(
|
||||
choices=[(1, "1"), (2, "2"), (3, "3"), (4, "4"), (5, "N")],
|
||||
verbose_name="Year",
|
||||
),
|
||||
),
|
||||
(
|
||||
"text",
|
||||
models.TextField(default="", max_length=300, verbose_name="Text"),
|
||||
),
|
||||
(
|
||||
"custom_role_name",
|
||||
models.CharField(
|
||||
blank=True, max_length=255, verbose_name="Custom role name"
|
||||
),
|
||||
),
|
||||
(
|
||||
"custom_role_is_board",
|
||||
models.BooleanField(verbose_name="Board member"),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Kaehmylomake',
|
||||
'verbose_name_plural': 'Kaehmylomakkeet',
|
||||
"verbose_name": "Kaehmylomake",
|
||||
"verbose_name_plural": "Kaehmylomakkeet",
|
||||
},
|
||||
bases=('kaehmy.commentparent',),
|
||||
bases=("kaehmy.commentparent",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CustomRole',
|
||||
name="CustomRole",
|
||||
fields=[
|
||||
('kaehmybaserole_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='kaehmy.KaehmyBaseRole')),
|
||||
(
|
||||
"kaehmybaserole_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="kaehmy.KaehmyBaseRole",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Custom kaehmy role',
|
||||
'verbose_name_plural': 'Custom kaehmy roles',
|
||||
"verbose_name": "Custom kaehmy role",
|
||||
"verbose_name_plural": "Custom kaehmy roles",
|
||||
},
|
||||
bases=('kaehmy.kaehmybaserole',),
|
||||
bases=("kaehmy.kaehmybaserole",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PresetRole',
|
||||
name="PresetRole",
|
||||
fields=[
|
||||
('kaehmybaserole_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='kaehmy.KaehmyBaseRole')),
|
||||
('description', models.TextField(verbose_name='Description')),
|
||||
(
|
||||
"kaehmybaserole_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="kaehmy.KaehmyBaseRole",
|
||||
),
|
||||
),
|
||||
("description", models.TextField(verbose_name="Description")),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Preset kaehmy role',
|
||||
'verbose_name_plural': 'Preset kaehmy roles',
|
||||
"verbose_name": "Preset kaehmy role",
|
||||
"verbose_name_plural": "Preset kaehmy roles",
|
||||
},
|
||||
bases=('kaehmy.kaehmybaserole',),
|
||||
bases=("kaehmy.kaehmybaserole",),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='application',
|
||||
name='custom_roles',
|
||||
field=models.ManyToManyField(blank=True, related_name='forms', to='kaehmy.CustomRole'),
|
||||
model_name="application",
|
||||
name="custom_roles",
|
||||
field=models.ManyToManyField(
|
||||
blank=True, related_name="forms", to="kaehmy.CustomRole"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='application',
|
||||
name='preset_roles',
|
||||
field=models.ManyToManyField(blank=True, related_name='forms', to='kaehmy.PresetRole'),
|
||||
model_name="application",
|
||||
name="preset_roles",
|
||||
field=models.ManyToManyField(
|
||||
blank=True, related_name="forms", to="kaehmy.PresetRole"
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -6,13 +6,32 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('kaehmy', '0003_auto_20180902_1943'),
|
||||
("kaehmy", "0003_auto_20180902_1943"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='kaehmybaserole',
|
||||
name='category',
|
||||
field=models.CharField(choices=[('corporate', 'Corporate affairs'), ('freshman', 'Freshmen'), ('international', 'International'), ('external', 'External affairs'), ('media', 'Media'), ('tech', 'Technology'), ('wellbeing', 'Wellbeing'), ('elepaja', 'Elepaja'), ('ceremonies', 'Ceremonies'), ('studies', 'Studies'), ('sosso', 'Sössö magazine'), ('alumni', 'Alumni relations'), ('others', 'Others')], default='others', max_length=255, verbose_name='Category'),
|
||||
model_name="kaehmybaserole",
|
||||
name="category",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("corporate", "Corporate affairs"),
|
||||
("freshman", "Freshmen"),
|
||||
("international", "International"),
|
||||
("external", "External affairs"),
|
||||
("media", "Media"),
|
||||
("tech", "Technology"),
|
||||
("wellbeing", "Wellbeing"),
|
||||
("elepaja", "Elepaja"),
|
||||
("ceremonies", "Ceremonies"),
|
||||
("studies", "Studies"),
|
||||
("sosso", "Sössö magazine"),
|
||||
("alumni", "Alumni relations"),
|
||||
("others", "Others"),
|
||||
],
|
||||
default="others",
|
||||
max_length=255,
|
||||
verbose_name="Category",
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.1.5 on 2019-03-12 12:58
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("kaehmy", "0004_auto_20181018_2121"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="application",
|
||||
name="custom_role_is_board",
|
||||
field=models.BooleanField(blank=True, verbose_name="Board member"),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,16 @@
|
||||
# Generated by Django 2.2.26 on 2022-01-12 20:38
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("kaehmy", "0005_auto_20190312_1458"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name="TelegramChannel",
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.14 on 2022-08-01 19:13
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("kaehmy", "0006_delete_telegramchannel"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="commentparent",
|
||||
name="id",
|
||||
field=models.AutoField(primary_key=True, serialize=False),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,44 @@
|
||||
# Generated by Django 3.2.14 on 2022-08-03 20:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("kaehmy", "0007_alter_commentparent_id"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="BaseRole",
|
||||
fields=[
|
||||
("id", models.AutoField(primary_key=True, serialize=False)),
|
||||
("name", models.CharField(max_length=255, verbose_name="Name")),
|
||||
("is_board", models.BooleanField(verbose_name="Board member")),
|
||||
(
|
||||
"category",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("corporate", "Corporate affairs"),
|
||||
("freshman", "Freshmen"),
|
||||
("international", "International"),
|
||||
("external", "External affairs"),
|
||||
("media", "Media"),
|
||||
("tech", "Technology"),
|
||||
("wellbeing", "Wellbeing"),
|
||||
("elepaja", "Elepaja"),
|
||||
("ceremonies", "Ceremonies"),
|
||||
("studies", "Studies"),
|
||||
("sosso", "Sössö magazine"),
|
||||
("alumni", "Alumni relations"),
|
||||
("others", "Others"),
|
||||
],
|
||||
default="others",
|
||||
max_length=255,
|
||||
verbose_name="Category",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,29 @@
|
||||
# Generated by Django 2.2.28 on 2022-07-26 17:15
|
||||
|
||||
from unicodedata import category
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def copyBaseRolesToNewTable(apps, schema_editor):
|
||||
Old = apps.get_model("kaehmy", "KaehmyBaseRole")
|
||||
New = apps.get_model("kaehmy", "BaseRole")
|
||||
for bases in Old.objects.all():
|
||||
New.objects.create(
|
||||
id=bases.id,
|
||||
name=bases.name,
|
||||
is_board=bases.is_board,
|
||||
category=bases.category,
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("kaehmy", "0008_baserole"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
copyBaseRolesToNewTable, reverse_code=migrations.RunPython.noop
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,51 @@
|
||||
# Generated by Django 2.2.28 on 2022-07-26 17:33
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
from sikweb.custom_operations import AlterModelBases
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("kaehmy", "0009_auto_20220726_2015"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
AlterModelBases("customrole", (models.Model,)),
|
||||
AlterModelBases("presetrole", (models.Model,)),
|
||||
migrations.AlterField(
|
||||
model_name="customrole",
|
||||
name="kaehmybaserole_ptr",
|
||||
field=models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="BaseRole",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="presetrole",
|
||||
name="kaehmybaserole_ptr",
|
||||
field=models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="BaseRole",
|
||||
),
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="customrole",
|
||||
old_name="kaehmybaserole_ptr",
|
||||
new_name="baserole_ptr",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="presetrole",
|
||||
old_name="kaehmybaserole_ptr",
|
||||
new_name="baserole_ptr",
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,16 @@
|
||||
# Generated by Django 3.2.14 on 2022-08-03 20:19
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("kaehmy", "0010_auto_20220726_2033"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name="KaehmyBaseRole",
|
||||
),
|
||||
]
|
||||
@@ -1,64 +1,71 @@
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
from webapp.utils import month_from_now
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from auditlog.registry import auditlog
|
||||
from phonenumber_field.modelfields import PhoneNumberField
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
import logging
|
||||
|
||||
import webapp.models
|
||||
|
||||
VERBOSE_NAME = _('Kaehmy')
|
||||
VERBOSE_NAME = _("Kaehmy")
|
||||
|
||||
|
||||
class KaehmyBaseRole(webapp.models.BaseRole):
|
||||
class BaseRole(models.Model):
|
||||
"""Base model for occupations/roles."""
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = models.CharField(_("Name"), max_length=255)
|
||||
is_board = models.BooleanField(_("Board member"))
|
||||
|
||||
CATEGORIES = (
|
||||
('corporate', _('Corporate affairs')),
|
||||
('freshman', _('Freshmen')),
|
||||
('international', _('International')),
|
||||
('external', _('External affairs')),
|
||||
('media', _('Media')),
|
||||
('tech', _('Technology')),
|
||||
('wellbeing', _('Wellbeing')),
|
||||
('elepaja', _('Elepaja')),
|
||||
('ceremonies', _('Ceremonies')),
|
||||
('studies', _('Studies')),
|
||||
('sosso', _('Sössö magazine')),
|
||||
('alumni', _('Alumni relations')),
|
||||
('others', _('Others')),
|
||||
("board", _("Board")),
|
||||
("corporate", _("Corporate affairs")),
|
||||
("freshman", _("Freshmen")),
|
||||
("international", _("International")),
|
||||
("siwa", _("SIK's free time")),
|
||||
("media", _("Media")),
|
||||
("tech", _("Technology")),
|
||||
("wellbeing", _("Wellbeing")),
|
||||
("sikpaja", _("Sik-paja")),
|
||||
("ceremonies", _("Ceremonies")),
|
||||
("studies", _("Studies")),
|
||||
("sosso", _("Sössö magazine")),
|
||||
("pota", _("PoTa")),
|
||||
("alumni", _("Alumni relations")),
|
||||
("n", _("N")),
|
||||
("others", _("Others")),
|
||||
)
|
||||
category = models.CharField(_('Category'), choices=CATEGORIES, default='others', max_length=255)
|
||||
category = models.CharField(
|
||||
_("Category"), choices=CATEGORIES, default="others", max_length=255
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
n = self.name.capitalize()
|
||||
return "{} ({})".format(n, _("board member")) if self.is_board else n
|
||||
|
||||
|
||||
class PresetRole(KaehmyBaseRole):
|
||||
class PresetRole(BaseRole):
|
||||
"""Model for kaehmy role."""
|
||||
|
||||
description = models.TextField(_('Description'))
|
||||
description = models.TextField(_("Description"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Preset kaehmy role')
|
||||
verbose_name_plural = _('Preset kaehmy roles')
|
||||
verbose_name = _("Preset kaehmy role")
|
||||
verbose_name_plural = _("Preset kaehmy roles")
|
||||
|
||||
|
||||
class CustomRole(KaehmyBaseRole):
|
||||
class CustomRole(BaseRole):
|
||||
"""Model representing a user-specified custom occupation."""
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Custom kaehmy role')
|
||||
verbose_name_plural = _('Custom kaehmy roles')
|
||||
verbose_name = _("Custom kaehmy role")
|
||||
verbose_name_plural = _("Custom kaehmy roles")
|
||||
|
||||
|
||||
class CommentParent(models.Model):
|
||||
|
||||
name = models.CharField(_('Name'), max_length=255, default='')
|
||||
email = models.EmailField(_('Email'), default='')
|
||||
timestamp = models.DateTimeField(_('Timestamp'), default=timezone.now)
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = models.CharField(_("Name"), max_length=255, default="")
|
||||
email = models.EmailField(_("Email"), default="")
|
||||
timestamp = models.DateTimeField(_("Timestamp"), default=timezone.now)
|
||||
|
||||
def __str__(self):
|
||||
return 'Message parent #{}'.format(self.id)
|
||||
return "Message parent #{}".format(self.id)
|
||||
|
||||
|
||||
class Comment(CommentParent):
|
||||
@@ -69,11 +76,13 @@ class Comment(CommentParent):
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Kaehmykommentti')
|
||||
verbose_name_plural = _('Kaehmykommentit')
|
||||
verbose_name = _("Kaehmykommentti")
|
||||
verbose_name_plural = _("Kaehmykommentit")
|
||||
|
||||
message = models.TextField(_('Message'))
|
||||
parent = models.ForeignKey('CommentParent', related_name='messages', on_delete=models.CASCADE)
|
||||
message = models.TextField(_("Message"))
|
||||
parent = models.ForeignKey(
|
||||
"CommentParent", related_name="messages", on_delete=models.CASCADE
|
||||
)
|
||||
|
||||
|
||||
class Application(CommentParent):
|
||||
@@ -82,34 +91,36 @@ class Application(CommentParent):
|
||||
|
||||
Allows user to choose from existing roles or to create custom ones.
|
||||
"""
|
||||
|
||||
YEAR_CHOICES = (
|
||||
(1, '1'),
|
||||
(2, '2'),
|
||||
(3, '3'),
|
||||
(4, '4'),
|
||||
(5, 'N'),
|
||||
(1, "1"),
|
||||
(2, "2"),
|
||||
(3, "3"),
|
||||
(4, "4"),
|
||||
(5, "N"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Kaehmylomake')
|
||||
verbose_name_plural = _('Kaehmylomakkeet')
|
||||
verbose_name = _("Kaehmylomake")
|
||||
verbose_name_plural = _("Kaehmylomakkeet")
|
||||
|
||||
phone_number = models.CharField(
|
||||
_('Phone number'), max_length=10, default="")
|
||||
year = models.IntegerField(_('Year'), choices=YEAR_CHOICES)
|
||||
text = models.TextField(_('Text'), default="", max_length=300)
|
||||
phone_number = models.CharField(_("Phone number"), max_length=10, default="")
|
||||
year = models.IntegerField(_("Year"), choices=YEAR_CHOICES)
|
||||
text = models.TextField(_("Text"), default="", max_length=300)
|
||||
custom_role_name = models.CharField(
|
||||
_('Custom role name'), max_length=255, blank=True)
|
||||
custom_role_is_board = models.BooleanField(
|
||||
_('Board member'), blank=True)
|
||||
_("Custom role name"), max_length=255, blank=True
|
||||
)
|
||||
custom_role_is_board = models.BooleanField(_("Board member"), blank=True)
|
||||
custom_roles = models.ManyToManyField(
|
||||
'kaehmy.CustomRole', related_name='forms', blank=True)
|
||||
"kaehmy.CustomRole", related_name="forms", blank=True
|
||||
)
|
||||
preset_roles = models.ManyToManyField(
|
||||
'kaehmy.PresetRole', related_name='forms', blank=True)
|
||||
"kaehmy.PresetRole", related_name="forms", blank=True
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
"""Return model info."""
|
||||
return _('Kaehmy application: {}').format(self.name)
|
||||
return _("Kaehmy application: {}").format(self.name)
|
||||
|
||||
def comment_count(self):
|
||||
"""Count comments for kaehmy."""
|
||||
@@ -131,34 +142,27 @@ class Application(CommentParent):
|
||||
presets = [r.name.capitalize() for r in self.preset_roles.filter(is_board=True)]
|
||||
customs = [r.name.capitalize() for r in self.custom_roles.filter(is_board=True)]
|
||||
combined = presets + customs
|
||||
return _('Board: {}').format(', '.join(combined)) if len(combined) > 0 else ''
|
||||
return _("Board: {}").format(", ".join(combined)) if len(combined) > 0 else ""
|
||||
|
||||
def official_roles(self):
|
||||
presets = [r.name.capitalize() for r in self.preset_roles.filter(is_board=False)]
|
||||
customs = [r.name.capitalize() for r in self.custom_roles.filter(is_board=False)]
|
||||
presets = [
|
||||
r.name.capitalize() for r in self.preset_roles.filter(is_board=False)
|
||||
]
|
||||
customs = [
|
||||
r.name.capitalize() for r in self.custom_roles.filter(is_board=False)
|
||||
]
|
||||
combined = presets + customs
|
||||
return _('Official: {}').format(', '.join(combined)) if len(combined) > 0 else ''
|
||||
return (
|
||||
_("Official: {}").format(", ".join(combined)) if len(combined) > 0 else ""
|
||||
)
|
||||
|
||||
def all_roles(self):
|
||||
presets = [r.name.capitalize() for r in self.preset_roles.all()]
|
||||
customs = [r.name.capitalize() for r in self.custom_roles.all()]
|
||||
combined = presets + customs
|
||||
return ', '.join(combined) if len(combined) > 0 else ''
|
||||
return ", ".join(combined) if len(combined) > 0 else ""
|
||||
|
||||
def has_any_board_role(self):
|
||||
return self.preset_roles.filter(is_board=True).exists() or self.custom_roles.filter(is_board=True)
|
||||
|
||||
|
||||
# Telegram channel entry for Kaehmys
|
||||
class TelegramChannel(models.Model):
|
||||
"""Model containing the channel id of a Telegram chat"""
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Telegram channel')
|
||||
verbose_name_plural = _('Telegram channels')
|
||||
|
||||
name = models.CharField(max_length=255)
|
||||
channel_id = models.CharField(max_length=255, unique=True)
|
||||
|
||||
def __str__(self):
|
||||
return 'Telegram channel: "{}"'.format(self.name)
|
||||
return self.preset_roles.filter(
|
||||
is_board=True
|
||||
).exists() or self.custom_roles.filter(is_board=True)
|
||||
|
||||
@@ -5,12 +5,6 @@
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
body {
|
||||
max-width: 1000px;
|
||||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
}
|
||||
|
||||
div.tooltip-inner {
|
||||
max-width: 25rem;
|
||||
}
|
||||
@@ -28,6 +22,10 @@ div.tooltip-inner {
|
||||
.kaehmy-content {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
max-width: 1000px;
|
||||
width: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
p {
|
||||
|
||||
@@ -3,13 +3,9 @@
|
||||
}
|
||||
|
||||
footer {
|
||||
/* position: absolute; */
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 60px; /* Set the fixed height of the footer here */
|
||||
/* line-height: 60px; /* Vertically center the text there */
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
footer .container .col .nav .nav-item {
|
||||
@@ -26,6 +22,7 @@ footer .container .col .nav .nav-item {
|
||||
|
||||
.lang-select {
|
||||
width: 10rem;
|
||||
margin-bottom: 1rem;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +1,28 @@
|
||||
.header-content {
|
||||
|
||||
.kaehmy-header {
|
||||
background-color: #0c2938;
|
||||
}
|
||||
|
||||
.header-content .logo {
|
||||
.kaehmy-header-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
}
|
||||
|
||||
.header-content .logo img {
|
||||
display: block;
|
||||
height: auto;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.kaehmy-banner {
|
||||
max-width: 1000px;
|
||||
width: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1000px) {
|
||||
.kaehmy_header-content {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background-color: #052f5f;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.kaehmy_header {
|
||||
margin-bottom: 331px;
|
||||
}
|
||||
}
|
||||
|
||||
.kaehmy-banner-image {
|
||||
width: 100%;
|
||||
max-height: 10rem;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.heading {
|
||||
display: flex;
|
||||
place-content: center;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
margin: 1rem;
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
.kaehmy_navigation {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.navbar-border {
|
||||
border-bottom: 2px solid #282b3b;
|
||||
}
|
||||
|
||||
.navbar-light .navbar-nav .nav-link {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
max-width: 1000px;
|
||||
width: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 88 KiB |
@@ -1,6 +1,6 @@
|
||||
import django_tables2 as tables
|
||||
from django.db.models import Count, Q
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from kaehmy.models import Application
|
||||
|
||||
@@ -8,6 +8,12 @@ from kaehmy.models import Application
|
||||
class ExportTable(tables.Table):
|
||||
class Meta:
|
||||
model = Application
|
||||
exclude = ['text', 'messageparent_ptr', 'custom_role_name', 'custom_role_is_board', 'timestamp']
|
||||
exclude = [
|
||||
"text",
|
||||
"messageparent_ptr",
|
||||
"custom_role_name",
|
||||
"custom_role_is_board",
|
||||
"timestamp",
|
||||
]
|
||||
|
||||
all_roles = tables.Column(verbose_name=_('Roles'), orderable=False)
|
||||
all_roles = tables.Column(verbose_name=_("Roles"), orderable=False)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{% if wrap_label %}
|
||||
<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>{% endif %}
|
||||
<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>
|
||||
{% include "django/forms/widgets/input.html" %}
|
||||
{% if wrap_label %} {{ widget.label }}</label>{% endif %}
|
||||
{{ widget.label }}</label>
|
||||
<span class="fa fa-info-circle" data-toggle="tooltip" data-placement="right" title="{{ widget.description }}"></span>
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load staticfiles %}
|
||||
|
||||
<footer style="text-align: center">
|
||||
<div>
|
||||
<form class="lang-form form" action="{% url 'set_language' %}" method="post">{% csrf_token %}
|
||||
<span>
|
||||
<input name="next" type="hidden" value="{{ redirect_to }}" />
|
||||
<select onchange="this.form.submit()" class="lang-select form-control" name="language">
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
{% get_available_languages as LANGUAGES %}
|
||||
{% get_language_info_list for LANGUAGES as languages %}
|
||||
{% for language in languages %}
|
||||
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
|
||||
{{ language.name_local }} ({{ language.code }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</span>
|
||||
</form>
|
||||
<span>{% trans "Copyright Aalto-yliopiston Sähköinsinöörikilta ry" %} {% now 'Y' %}</span>
|
||||
</div>
|
||||
</footer>
|
||||
@@ -1,7 +0,0 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div class="kaehmy_header-content">
|
||||
<div class="kaehmy-banner logo">
|
||||
<a href="/kaehmy"><img class="kaehmy-banner-image" src="/static/img/kaehmy_banner.png" alt="Aalto-yliopiston Sähköinsinöörikilta ry"></a>
|
||||
</div>
|
||||
</div>
|
||||