Compare commits
796 Commits
4e953c83ea
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| 656a24ef02 | |||
| 74643db087 | |||
| 3a6fd3cee1 | |||
| 29e9d1bd8c | |||
| 3c5ca9ae00 | |||
| 3d1ad6112d | |||
| b843e67e9b | |||
| 4cab71e8fb | |||
| 969339a95f | |||
| e06efa1539 | |||
| 2fb6d2782f | |||
| f69eccd0e4 | |||
| e20e6d4b1d | |||
| 0352a60e28 | |||
| abeb14601a | |||
| f52225495d | |||
| 797b2c15f7 | |||
| e81618500b | |||
| f7a5da306c | |||
| 92a9b5c6c9 | |||
| 964aeb97a7 | |||
| 474e9eb0f8 | |||
| 7acc2c6eec | |||
| 5a02837d7f | |||
| 7ff0a9b489 | |||
| 9af4b58a4f | |||
| 7a20890f17 | |||
| f6c9e532f8 | |||
| f1698c4fd3 | |||
| cb82aeffde | |||
| e9c1ef2604 | |||
| d8f0f2b3be | |||
| 65f065f5ce | |||
| 5cba64e500 | |||
| 064784a222 | |||
| b890ef3563 | |||
| 962f2ae86c | |||
| c71a1940dd | |||
| 8b2913a8be | |||
| 111ffc2b2e | |||
| 600aa918ef | |||
| 68e13ed176 | |||
| 0fa4330f2c | |||
| c9289b1e8c | |||
| bb3136bfc7 | |||
| b708e9ecaf | |||
| f3619b2881 | |||
| 7572c92da5 | |||
| 5a2b11b44e | |||
| df15ea100b | |||
| 3da6e90f73 | |||
| f784ab0c78 | |||
| ebaa477cff | |||
| b4be21bf45 | |||
| 0df36df9d8 | |||
| a5754b2633 | |||
| 7c6e01a436 | |||
| ad90429ec7 | |||
| 0e36237890 | |||
| 3261f5a90a | |||
| 1551c1bdf2 | |||
| 996bd7af67 | |||
| 21bd34c94d | |||
| fb581c940b | |||
| b428f46213 | |||
| 38fe55dd86 | |||
| 0da6c425fd | |||
| 81962ab9e7 | |||
| 48dd0a6a19 | |||
| 661827a957 | |||
| 242de5f749 | |||
| bd7f940987 | |||
| 0634671c84 | |||
| 1fb5be0cf8 | |||
| 3f9e4265e5 | |||
| de21b8b5e5 | |||
| fd481fef2e | |||
| 70f077e393 | |||
| 1c7d943a21 | |||
| 41873ebfe5 | |||
| fc2dbde064 | |||
| a372be4af2 | |||
| 5d333b28ab | |||
| 84ad047c01 | |||
| c93b2631cb | |||
| 15dd06a91f | |||
| 30ff26c7ef | |||
| 1434e7502a | |||
| 93b21fb7d0 | |||
| e5c82f392c | |||
| 0626964461 | |||
| 23a724e390 | |||
| 2a9c7cf854 | |||
| 335630e16d | |||
| 6051f7c294 | |||
| c1ea6cd211 | |||
| 6c43b46007 | |||
| dc9e68c4b9 | |||
| 4b03f99971 | |||
| 426f4b3d8b | |||
| 3604233507 | |||
| 8c5099f14a | |||
| d5bc348453 | |||
| bce98cb439 | |||
| 1ed3d27533 | |||
| 39a098af8e | |||
| 62491b84c1 | |||
| 81f7f5bb5d | |||
| 8ce4122160 | |||
| 370ad2ce66 | |||
| f25c425d85 | |||
| d921623f31 | |||
| 2589f1c703 | |||
| 0edb9094c4 | |||
| bc8feba701 | |||
| f37d74a7d1 | |||
| fa8612ad1a | |||
| 1d8a054b06 | |||
| 5898fbf86d | |||
| cd1cdd2e0b | |||
| c0f920544b | |||
| 36c90531a8 | |||
| 7f7c5a3b04 | |||
| c084e56ad8 | |||
| 84acc3c76e | |||
| e1f0014898 | |||
| 05b3a470f3 | |||
| ebe060646a | |||
| bb412be8d3 | |||
| e3c48eac24 | |||
| da89cdceda | |||
| 5a6c2c99e5 | |||
| 9f53836ce8 | |||
| 5d53d1a1dc | |||
| e00dda1dc2 | |||
| a93e0c819f | |||
| c87733b37a | |||
| 9aa964bf05 | |||
| dcb1d3ec15 | |||
| 5d9b8f3213 | |||
| d12989d195 | |||
| a9f384b50e | |||
| afedf2d0bd | |||
| a4b8486bd4 | |||
| d8bcb8ece6 | |||
| b01ac219a3 | |||
| 42320866c4 | |||
| e2e6c14d57 | |||
| 4761c38cd2 | |||
| e2bef3efe2 | |||
| bbfd4c3800 | |||
| b671d8fbb4 | |||
| 1ea04e98e8 | |||
| c1a7d6790b | |||
| f519f78922 | |||
| 551b5ed6be | |||
| 20cbb0397a | |||
| 26f999c4cf | |||
| 9858dfc1bd | |||
| 2d1df879dc | |||
| 2f12dc6a5e | |||
| 4b3286f12d | |||
| d53c5707b8 | |||
| ba64661217 | |||
| 554de17e9e | |||
| 5bc4b538e6 | |||
| 8a691d59e7 | |||
| e99798ba5c | |||
| caa962700b | |||
| 648466aa70 | |||
| 25f84bf2ad | |||
| ac2147095a | |||
| 4856c720b1 | |||
| ba8ff743f2 | |||
| 1e827af2dd | |||
| 7d107abc6a | |||
| 3bb1cd29cd | |||
| 0388367c7a | |||
| a8843dfc8f | |||
| 9d1264b6e6 | |||
| 8df04519b9 | |||
| 112a731cd6 | |||
| 8b4ff83921 | |||
| 2249b615f4 | |||
| fbfc800453 | |||
| 752aaf9b89 | |||
| f1a0d5f475 | |||
| fb5f38b3e6 | |||
| ded5299387 | |||
| 090548905f | |||
| 12a89b6927 | |||
| fc4c348ff9 | |||
| 1cd133e335 | |||
| b5dc6ca97d | |||
| f7b98c9dfe | |||
| 8e9f4e2b2e | |||
| b3faa06c4c | |||
| 165aeb6dcc | |||
| 24bae28cec | |||
| 65182d4c2f | |||
| 1d879993c9 | |||
| f3fbf3ba1d | |||
| 5a24f32327 | |||
| d72bd22f6d | |||
| 0eb94038f6 | |||
| 3a69397c0a | |||
| d49cb5a783 | |||
| c13805dd75 | |||
| bcf8a0e6e4 | |||
| 81f9398da4 | |||
| 32c90aecd3 | |||
| 01d6c1e0f6 | |||
| 607a442e22 | |||
| b2a79b3547 | |||
| 63c692d46b | |||
| 3d2ef9e735 | |||
| aee8b0c1e8 | |||
| d5e28ba3d9 | |||
| 1edaf4df14 | |||
| b6d31e3c3b | |||
| ed7b55c090 | |||
| e66e9ad888 | |||
| 4ad8b30e04 | |||
| ae2ac5c462 | |||
| 2330542a85 | |||
| 7363e1ab30 | |||
| d2131b2c91 | |||
| 78866c86cd | |||
| f5dbccb9c4 | |||
| 8ab38cc71b | |||
| 7dfcbfe38f | |||
| cd8471036c | |||
| c3ec477a6e | |||
| 5a6294adf6 | |||
| 1ba44cdd67 | |||
| dfeb88f980 | |||
| 44a724809f | |||
| 1acd4be953 | |||
| 930a5383c4 | |||
| eda6da7f12 | |||
| 448fc395d8 | |||
| 7e8b665c7c | |||
| 7be61bc9b8 | |||
| 6828af76dc | |||
| fe92d762be | |||
| b91a17e950 | |||
| aabc549bcf | |||
| 4bb4d0386b | |||
| a74af5e4d3 | |||
| a939d53286 | |||
| 35c6aae552 | |||
| 83bd6cf7e6 | |||
| 17a0cfbde0 | |||
| dbcad42da0 | |||
| 1b48022b63 | |||
| 7b8e3061d5 | |||
| 3f27564075 | |||
| e7c2746eab | |||
| 1e243496fb | |||
| 53bc433aaa | |||
| 6f5e75a1b3 | |||
| f83851b694 | |||
| 3f5a5dceb5 | |||
| f9c7dd8c39 | |||
| f3e437dbd1 | |||
| 5ee1e61eac | |||
| abd34ec7cb | |||
| b73f6db7b6 | |||
| 3b9f10dad7 | |||
|
|
53c0e8b3b8 | ||
|
|
7d264fe131 | ||
|
|
c968b39657 | ||
|
|
ebf116f347 | ||
| 8227866e7a | |||
| 6f5e73b533 | |||
| a302a36fd4 | |||
| 1307b2ff7b | |||
| d9730e765e | |||
| 8420c698d4 | |||
| 14be917d43 | |||
| 20da09fb96 | |||
| 19de7d3e4c | |||
| 41d821b86e | |||
| b758b54233 | |||
| c650266c6b | |||
| dae9bb0916 | |||
| 2f2371d8df | |||
| baaf4b70ac | |||
| 3b1cd800f6 | |||
| 0f5f7216ac | |||
| c40872379e | |||
| 897ac5ceef | |||
| eb3dbb3e45 | |||
| 9eb6042ba7 | |||
| 075833aa25 | |||
| f84d800bff | |||
| 20f814b0ef | |||
| 4376a63e93 | |||
| ee3d316175 | |||
| 452113b4bf | |||
| e9b28ea1c1 | |||
| ba07533667 | |||
| 1ab5c4885e | |||
| 8c977cf255 | |||
| d4c6014e17 | |||
| 078e5e28cc | |||
| 7010b4f3d2 | |||
| 43f38b88ce | |||
| 7a12a1a4d6 | |||
| d784f14c4c | |||
| 339cdf3ea9 | |||
| 060be3b486 | |||
| 9f93a19d51 | |||
| c131c07afe | |||
| 42dbf5c6f7 | |||
| 2e9039a569 | |||
| 64b48efafb | |||
| 37e8dc4bdc | |||
| 61deb96961 | |||
| 3a6ce1d38b | |||
| 82fb73ae59 | |||
| 4e71c8704f | |||
| 0c1edf647b | |||
| 9b38898a8a | |||
| 25348e45e0 | |||
| 631c2360e6 | |||
| 6798cf3477 | |||
| cc873d6029 | |||
| 5d147a4fc9 | |||
| 640862d8ee | |||
| 99bb53a7a3 | |||
| 4f05dc18b9 | |||
| 2a2df3bf52 | |||
| c2b15c2175 | |||
| edc27b899e | |||
| 59d96e36a4 | |||
| 2c976f926c | |||
| 671c6ec6f5 | |||
| ef9ac58c0f | |||
| 60e6fdf4e4 | |||
| 06e6455ba0 | |||
| 007eb3b5a9 | |||
| f3333f2da4 | |||
| 96b40c5169 | |||
| d81408b79c | |||
| 5ae5e90461 | |||
| 2534ef3319 | |||
| 0c2e774891 | |||
| 895bb3c901 | |||
| fca5445aa7 | |||
| 31d2b85b2f | |||
| a8b3214c49 | |||
| ccdfd388c4 | |||
| cad6acd125 | |||
| 8dc9c1b9e7 | |||
| cd4de2528f | |||
| 3ef4b98c1c | |||
| 349917e887 | |||
| 6c200ba076 | |||
| f16aa845d2 | |||
| 3bccb1e690 | |||
| 10ae697e33 | |||
| baf0d2db72 | |||
| b30123a890 | |||
| 44c34d2daa | |||
| e010fa413b | |||
| e79aca4efa | |||
| 037f6529fd | |||
| 14752d9746 | |||
| a8b2bd4e90 | |||
| 60ae971f14 | |||
| 1920d72821 | |||
| 01aa8baadd | |||
| f2f526c9de | |||
| 4d490690e2 | |||
| d9c7aa8c49 | |||
| 040299b90c | |||
| 0bd321e5ec | |||
| c038370602 | |||
| c08f7fc792 | |||
| 4dd35c3866 | |||
| 8bd041d7ea | |||
| d450ad42c0 | |||
| d34dcada09 | |||
| d8448de419 | |||
| 35ef6676a2 | |||
| e132b1c9f6 | |||
| 5511d8275c | |||
| accf877375 | |||
| 9ac362fa58 | |||
| 9253fde2e5 | |||
| 975c962025 | |||
| ba72b4e59f | |||
| 89e001bd17 | |||
| 623ca8bc0a | |||
| 0b483ce630 | |||
| 16998b85d5 | |||
| b55952ac67 | |||
| 30967dac33 | |||
| 3166faa7eb | |||
| 9bba81be22 | |||
| 18a2d16bf6 | |||
| 9265cdaea9 | |||
| fcb9b60656 | |||
| 599702f50a | |||
| c8453db69d | |||
| 6ad93abe3b | |||
| 3c60782ae7 | |||
| 736f645bf0 | |||
| b0887ab731 | |||
| ada194122d | |||
| b3d1ec142b | |||
| e7a8a163f1 | |||
| c3ef54a267 | |||
| da3b43a713 | |||
| 8cfddd7882 | |||
| 80eafbb014 | |||
| cc2a659767 | |||
| cacfeff3fe | |||
| 9379728b71 | |||
| d30d15c0d4 | |||
| 5343f53661 | |||
| 3126b2b962 | |||
| 43c671018b | |||
| 7a37377a09 | |||
| 19d9dea8b1 | |||
| c50e0b18b5 | |||
| 4c07c0feb2 | |||
| cf15b60bef | |||
| 328f64aa51 | |||
| fdf4e79a69 | |||
| bbc8732112 | |||
| 17dbe85219 | |||
| 3dc011a22c | |||
| f5b89456ab | |||
| 2e4f63b250 | |||
| 3b261ff240 | |||
| f06b00fb9d | |||
| 88987a973e | |||
| 93ffbe09af | |||
| e11848ea72 | |||
| 8bc9d12bfa | |||
| 1dbfdccb89 | |||
| f085f5dcf5 | |||
| 33579e8446 | |||
| a852da365f | |||
| b53095ae17 | |||
| 3d7780e0ba | |||
| 478636bd98 | |||
| d9ebee1e07 | |||
| 23e154bce6 | |||
| 5624f59258 | |||
| 56df942dd0 | |||
| 2dcb5fbf88 | |||
| 7a84b470f9 | |||
| 76232b7a0f | |||
| 349af16075 | |||
| 8641bead80 | |||
| eb930b71d6 | |||
| ae4ba06abf | |||
| a2e237a81f | |||
| f90c8c7e8c | |||
| c316c74aff | |||
| 93dd0ae4f6 | |||
| f79bb355cf | |||
| 45a534a042 | |||
| 2106a3423f | |||
| d3f7274e92 | |||
| 5f576896b7 | |||
| 4a3cbfb8b0 | |||
| 3e93fe1a7a | |||
| 965e055ef1 | |||
| 13a0da6e46 | |||
| 1bb05dbf1c | |||
| 4c9c1e13a5 | |||
| 99cde15966 | |||
| f2edc23e75 | |||
| 8aab4a13ae | |||
| 226102ccaf | |||
| 3d088c55d7 | |||
| bb14a346cb | |||
| f387930dee | |||
| fe63e3b25c | |||
| 23adeb06e6 | |||
| c1bd458c80 | |||
| 2a1d4178d7 | |||
| f9a37b299d | |||
| 9950e87501 | |||
| eff1ba6513 | |||
| bb085aa9a8 | |||
| b0dc0f9d78 | |||
| d1a51b019c | |||
| b7fade55fb | |||
| 79461518a3 | |||
| 8059d5d23f | |||
| 3098eacfb4 | |||
| f3d1e1c203 | |||
| e6a985ddfa | |||
| 388cc327be | |||
| 13adc695f6 | |||
| f2c7943247 | |||
| 112fd52864 | |||
| 8279385966 | |||
| 1a9692949f | |||
| e7af49b309 | |||
| b822914db3 | |||
| 9ad33efe08 | |||
| bd8f9fc1b7 | |||
| 4a2c18be4d | |||
| 479aba0195 | |||
| 1299fcac84 | |||
| 884a07f87b | |||
| 6557e9f9eb | |||
| 602cef1302 | |||
| b400db603a | |||
| 0397311f6e | |||
| abce89c829 | |||
| bbad63a460 | |||
| d940630086 | |||
| 37ecf28f2f | |||
| 12d5a976cc | |||
| 9086e2e75b | |||
| 3607eb0e4e | |||
| 3daf83d725 | |||
| 5ad0cb74cc | |||
| 9ae64e8cb1 | |||
| 1b5a0c71e0 | |||
| 4d4f11c479 | |||
| 835c89d1d4 | |||
| 46bf07dd8d | |||
| f557672586 | |||
| 4e27e1be7f | |||
| 6d390ad21e | |||
| 2f2543160e | |||
| 64a9db133e | |||
| 712c3d32f3 | |||
| 8998bbdf6d | |||
| ff31caa139 | |||
| ad06829c31 | |||
| 03a48da355 | |||
| 885bed888d | |||
| 0051cb07c9 | |||
| 8858cff9cf | |||
| 70e2af6172 | |||
| 461abd2e46 | |||
|
|
d7269106db | ||
| 77fb99a527 | |||
| 38a56daa24 | |||
|
|
ac0749797f | ||
| f193f7d7ca | |||
| 43657e0862 | |||
| 68ad366f74 | |||
| 350d2c5da9 | |||
| 462bb8f485 | |||
| ea4d15b99a | |||
| de30dfcb8b | |||
| 36a979954c | |||
| 71ef17dc97 | |||
| 206cd282e6 | |||
| e399346c3e | |||
| 929c6dfff0 | |||
| 841b57fea2 | |||
| 9e5446ff1d | |||
| 3b79809b8c | |||
| 53e6db3655 | |||
| 424f91e919 | |||
| 84ce5f54b2 | |||
| a7e85212c0 | |||
| f1b3b660ff | |||
| 26cb60c1c8 | |||
| 69e58f1e0a | |||
| 5c33ac3833 | |||
| fccfd59ea3 | |||
| 50897b6d35 | |||
| 8edfe8c401 | |||
| 0d82dba414 | |||
| 2dc038dfef | |||
| c46a943c7f | |||
| 9f3592e64b | |||
| bc1f4e7ab7 | |||
| a2ef91e89a | |||
| 91d740511d | |||
| c6af3e8d04 | |||
| 0c94049e21 | |||
| 29f1d2f0f2 | |||
| 2578e96b32 | |||
| 907ed583cd | |||
| da51007b77 | |||
| 087f58c9ac | |||
| 860da7f06a | |||
| 457bee1ede | |||
| 3b37b5f588 | |||
| 6229f0f8a2 | |||
| b2a3d910d9 | |||
| 33848cbe15 | |||
| cc97fe32aa | |||
| 4576ac68e0 | |||
| 7c076e0bc3 | |||
| 74f54c7b31 | |||
| 87777cd5a4 | |||
| eee4cdf86b | |||
| b2d5265f7e | |||
| d4af2d88b4 | |||
| 8b4f5713e3 | |||
| 4bff268537 | |||
| 57da42e4bd | |||
| 2864d27a7f | |||
| 0a73b5099e | |||
| e3fb981542 | |||
| 5e80d75c91 | |||
| e3833b4505 | |||
| ab837ee80e | |||
| f6c1224dde | |||
| a78d671b6d | |||
| fb9c78d96a | |||
| 4ef9da953c | |||
| aefeffd63a | |||
| 81cc5cd53d | |||
| 002dded0d5 | |||
| ad6e2f4e17 | |||
| 160e7166f8 | |||
| 867319fe9a | |||
| 13b67c1248 | |||
| 4c4cf4afea | |||
| 5f742c60db | |||
| 568874e6dd | |||
| 561a30b7ab | |||
| a8c837e9f6 | |||
| a75cacea66 | |||
| b1e092769f | |||
| 5a93a1678c | |||
| 28772e1f74 | |||
| 1f3c3ecaef | |||
| ab1e6a94d1 | |||
| 299653b53b | |||
| fe9352e628 | |||
| 9fec95bd2e | |||
| 8e7cdafee0 | |||
| 6e2a2a1d5e | |||
| 5197875431 | |||
| d05bd45cf4 | |||
| 0afb2bb0ce | |||
| d17fcc1da2 | |||
| c508bc2cd1 | |||
| 20872e547b | |||
| 25b748d2be | |||
| 1536bb302a | |||
| d4ef706734 | |||
| 3bdce18e9e | |||
| 8b4488484d | |||
| 3881a4f3b4 | |||
| 2dbd908f4c | |||
| 9d0eed5915 | |||
| ee12bb5286 | |||
| 5669c822b9 | |||
| c1c4af6571 | |||
| 164ba7def2 | |||
| 7035b1642e | |||
| b6fc5c634f | |||
| 0dfbd614ab | |||
| 2730ff3f51 | |||
| fef211b2d0 | |||
| f2e2599561 | |||
| a9c0f628f7 | |||
| e2adb20231 | |||
| e8b3bf6516 | |||
| 3306f3e783 | |||
| b993621773 | |||
| 3816290eb7 | |||
| 399ecf73ad | |||
| 8e2c0e857c | |||
| 3c7dcb4c51 | |||
| 9e1ec1711b | |||
| bae4ee3d22 | |||
| 280eb83056 | |||
| fca5879aeb | |||
| 373a44c9da | |||
| 674645c65c | |||
| c2b3ff2395 | |||
| d6740eb302 | |||
| 35a54474b4 | |||
| 6723dad4bd | |||
| b51d04ffd1 | |||
| a965f26d48 | |||
| 364a6f32f4 | |||
| 533142461a | |||
| 481635ac4e | |||
| be6c30cb33 | |||
| a617137fb0 | |||
| 8299162a77 | |||
| 085162d802 | |||
| 27b7e47f18 | |||
| be97ac32fb | |||
| 9ea00655d4 | |||
| 9fffbffdb7 | |||
| 44cf2936d1 | |||
| 579f59580c | |||
| 241841bc9b | |||
| 78a6440f63 | |||
| 9d521b0129 | |||
| 39079c3c8e | |||
| 999c1a81b8 | |||
| 5a4720c41c | |||
| 858c6d4468 | |||
| 4b45b01e2a | |||
| d0060ecf5e | |||
| d1eeaafc42 | |||
| 9b824bc326 | |||
| 44f05cbb7d | |||
| 0e4e531414 | |||
| 6a7b3f19e9 | |||
| ec9f5b305c | |||
| e858f61b3f | |||
| a04270718f | |||
| a4f895de81 | |||
| b2d0e783be | |||
| 4f5022e140 | |||
| 5771968981 | |||
| b63b87872b | |||
| 1594b754cb | |||
| 8ec27191b6 | |||
| c1332ee1f0 | |||
| f6240a7189 | |||
| 7a02774a29 | |||
| 8945fdc0f4 | |||
| 9f0a18ad91 | |||
| e7f26dd23a | |||
| fc5b1391df | |||
| 70bf8e2053 | |||
| caf98ba60b | |||
| d7e466050a | |||
| 34b707ef20 | |||
| 064a9bf83a | |||
| 93070a3bcd | |||
| 23c35fe7dd | |||
| d2542060a1 | |||
| 89f74cb709 | |||
| ec38012ecb | |||
| 72d45a4f47 | |||
| 8de5f162eb | |||
| dc3859d589 | |||
| b4f52c7876 | |||
| 885622e581 | |||
| a42a3fa177 | |||
| 27541c6fb6 | |||
| 14547ad621 | |||
| 8d2d80c30e | |||
| e6f5a42d15 | |||
| 052e42f76a | |||
| 3eb7dbe984 | |||
| 202dfe46c2 | |||
| 01da0f1e29 | |||
| 8ccdf50bc5 | |||
| d46ab8da6b | |||
| 1dd53a87e9 | |||
| 40bb2e54bd | |||
| 433ad9d4b9 | |||
| 231c27819d | |||
| 890309564f | |||
| e1e1f822c8 | |||
| 7a788f4c90 | |||
| 7efa626b8b | |||
| 08e20e1875 | |||
| f1c79a5f94 | |||
| 5dd1991af8 | |||
| c0edef51bd | |||
| cb703e79ae | |||
| 87066b0cea | |||
| c4976c4b34 | |||
| ee46ff9cda | |||
| d4f27e8f2f | |||
| 4a6584370e | |||
| 82d3f95c99 | |||
| dce3d89c7e | |||
| 5520590145 | |||
| efabebfdbf | |||
| 6c52246bb7 | |||
| 2c11f7c385 | |||
| 9ee0bd8e30 | |||
| 1955476d24 | |||
| 05178da029 | |||
| 7a80cf8df1 | |||
| db94ec41ed | |||
| 5582538a70 | |||
| 7aa364fc38 | |||
| 96ce5963fe | |||
| bf54bc5d51 | |||
| 93ae172431 | |||
| 03d40a5092 | |||
| 993f8f9cd2 | |||
| 8efc0aad21 | |||
| 3a6e7f5344 | |||
| dac9661d51 | |||
| b9bfa8e359 | |||
| d07589464c | |||
| 1880da5151 |
4
.coveragerc
Normal file
@@ -0,0 +1,4 @@
|
||||
[run]
|
||||
omit =
|
||||
*/migrations/*
|
||||
*/tests/*
|
||||
10
.gitignore
vendored
@@ -2,11 +2,18 @@
|
||||
|
||||
# Database
|
||||
notfellchen
|
||||
*.sq3
|
||||
|
||||
# Geojson from imports
|
||||
*.geojson
|
||||
|
||||
# Media storage
|
||||
static
|
||||
/static
|
||||
media
|
||||
|
||||
# Compiled CSS
|
||||
/src/fellchensammlung/static/fellchensammlung/css/main.css
|
||||
/src/fellchensammlung/static/fellchensammlung/css/main.css.map
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
@@ -161,3 +168,4 @@ dmypy.json
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
/node_modules/
|
||||
|
||||
15
CHANGELOG.md
Normal file
@@ -0,0 +1,15 @@
|
||||
## Version 0.4.0
|
||||
|
||||
Version 0.4.0 has added support for search-as-you-type when searching for animals to adopt. Furthermore, the display of
|
||||
maps in the search has been majorly improved.
|
||||
|
||||
Photon has been added as geocoding source option which allows to use this functionality.
|
||||
|
||||
Further improvements include the representation of rescue organizations and tooltips.
|
||||
|
||||
One of the biggest features is the addition of search subscriptions. These allow you to not only
|
||||
search for currently active adoption notices but to subscribe to that search so that you get notified if there are new
|
||||
rats in your search area in the future.
|
||||
|
||||
For developers the new API documentation might come in handy, it can be found at
|
||||
[/api/schema/swagger-ui/](https://notfellchen.org/api/schema/swagger-ui/)
|
||||
@@ -1,6 +1,6 @@
|
||||
FROM python:3.11-slim
|
||||
# Use 3.11 to avoid django.core.exceptions.ImproperlyConfigured: Error loading psycopg2 or psycopg module
|
||||
MAINTAINER Julian-Samuel Gebühr
|
||||
LABEL org.opencontainers.image.authors="Julian-Samuel Gebühr"
|
||||
|
||||
ENV DOCKER_BUILD=true
|
||||
|
||||
|
||||
109
README.md
@@ -2,7 +2,7 @@
|
||||
|
||||
[notfellchen.org](https://notfellchen.org) ist eine Sammelstelle für Tier-Vermittlungen. Die Idee entstand, da in der
|
||||
deutschsprachigen Rattencommunity ein wilder Mix aus Websites, Foren und Facebookgruppen besteht die Ratten vermitteln.
|
||||
Diese Website soll die bestehende Communities NICHT ersetzten, jedoch ermöglichen, dass Menschen die Ratten aufnehmen
|
||||
Diese Website soll die bestehende Communitys NICHT ersetzten, jedoch ermöglichen, dass Menschen die Ratten aufnehmen
|
||||
wollen Informationen einfach finden und nicht bereits in jeder Gruppe sein müssen.
|
||||
|
||||
Wir nehmen Angebote auf die
|
||||
@@ -44,22 +44,63 @@ nf query_location <query>
|
||||
There is a system for customizing texts in Notfellchen. Not every change of a tet should mean an update of the software. But this should also not become a CMS.
|
||||
Therefore, a solution is used where a number of predefined texts per site are supported. These markdown texts will then be included in the site, if defined.
|
||||
|
||||
| Textcode | Location |
|
||||
|---------------------|----------|
|
||||
| `how_to` | Index |
|
||||
| `introduction` | Index |
|
||||
| `privacy_statement` | About |
|
||||
| `terms_of_service` | About |
|
||||
| `imprint` | About |
|
||||
| `about_us` | About |
|
||||
| Any rule | About |
|
||||
| Textcode | Location |
|
||||
|-------------------------|-----------------------|
|
||||
| `how_to` | Index |
|
||||
| `introduction` | Index |
|
||||
| `privacy_statement` | About |
|
||||
| `terms_of_service` | About |
|
||||
| `imprint` | About |
|
||||
| `about_us` | About |
|
||||
| `external_site_warning` | External Site Warning |
|
||||
| Any rule | About |
|
||||
|
||||
# Developer Notes
|
||||
|
||||
## Getting started
|
||||
|
||||
### Clone the project
|
||||
|
||||
```
|
||||
git clone https://codeberg.org/moanos/notfellchen.git
|
||||
```
|
||||
|
||||
### Install dependencies
|
||||
```
|
||||
pip install -e '.[all]'
|
||||
```
|
||||
|
||||
### Create the database
|
||||
|
||||
```
|
||||
nf migrate
|
||||
```
|
||||
|
||||
Because of a wired bug the initial migrations must run two times as the first time the permissions
|
||||
for `create_active_adoption_notice` are created but can not yet be accessed and on the second time this permission will
|
||||
be added to groups.
|
||||
|
||||
### Start the server
|
||||
|
||||
```
|
||||
nf runserver
|
||||
```
|
||||
|
||||
### Build the docs
|
||||
|
||||
```
|
||||
sphinx-autobuild ./docs ./docs/_build/html
|
||||
```
|
||||
|
||||
|
||||
## Styling
|
||||
|
||||
Bulma is used for styling, including related SCSS. All styles should eventually be migrated to SCSS.
|
||||
|
||||
Use `npm run build-bulma` to generate the css file from SCSS.
|
||||
You can use `npm start` during development so that the file is re-generated upon change.
|
||||
|
||||
|
||||
## Docker
|
||||
|
||||
Build latest image
|
||||
@@ -76,20 +117,37 @@ docker push moanos/notfellchen:latest
|
||||
docker run -p8000:7345 moanos/notfellchen:latest
|
||||
```
|
||||
|
||||
|
||||
## Testing
|
||||
|
||||
Tests can be run with
|
||||
|
||||
```zsh
|
||||
nf test src
|
||||
```
|
||||
|
||||
If you want to report on code coverage run
|
||||
|
||||
```zsh
|
||||
coverage run --source='.' src/manage.py test src
|
||||
```
|
||||
|
||||
and
|
||||
|
||||
```
|
||||
coverage report
|
||||
```
|
||||
|
||||
## Geocoding
|
||||
|
||||
Geocoding services (search map data by name, address or postcode) are provided via the
|
||||
[Nominatim](https://nominatim.org/) API, powered by [OpenStreetMap](https://openstreetmap.org) data. Notfellchen uses
|
||||
a selfhosted Nominatim instance to avoid overburdening the publicly hosted instance. Due to ressource constraints
|
||||
geocoding is only supported for Germany right now.
|
||||
|
||||
ToDos
|
||||
* [ ] Implement a report that shows the number of location strings that could not be converted into a location
|
||||
* [x] Add a management command to re-query location strings to fill location
|
||||
either [Nominatim](https://nominatim.org/) or [photon](https://github.com/komoot/photon) API, powered by [OpenStreetMap](https://openstreetmap.org) data.
|
||||
Notfellchen uses a selfhosted Photon instance to avoid overburdening the publicly hosted instance.
|
||||
|
||||
## Maps
|
||||
|
||||
The map on the main homepage is powered by [Versatiles](https://versatiles.org), and rendered using [Maplibre](https://maplibre.org/).
|
||||
The Versatiles server is self-hosted and does not send data to third parties.
|
||||
|
||||
## Translation
|
||||
|
||||
@@ -124,3 +182,20 @@ Start beat
|
||||
```zsh
|
||||
celery -A notfellchen.celery beat
|
||||
```
|
||||
|
||||
# Contributing
|
||||
|
||||
This project is currently mainly developed by me, moanos. I'd like that to change and will be very happy for contributions
|
||||
and shared responsibilities. Some ideas where you can look for contributing first
|
||||
|
||||
* UI improvements: Since a major redesign I'm much happier but the UI could use many, many little tweaks
|
||||
* Docker: If you know how to build a docker container that is a) smaller or b) utilizes staged builds this would be amazing. Any improvement welcome
|
||||
* Testing: Writing tests is always welcome, and it's likely you discover a few bugs
|
||||
|
||||
I'm also very happy for all other contributions. Before you do large refactoring efforts or features, best write a short
|
||||
issue for it before you spend a lot of work.
|
||||
|
||||
Send PRs either to [codeberg](https://codeberg.org/moanos/notfellchen) (preferred) or [GitHub](https://github.com/moan0s/notfellchen).
|
||||
CI (currently only for documentation) runs via [git.hyteck.de](https://git.hyteck.de), you can also ask moanos for an account there.
|
||||
|
||||
Also welcome are new issues with suggestions or bugs and additions to the documentation.
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
API Documentation
|
||||
*****************
|
||||
|
||||
The Notfellchen API serves the purpose of supporting 3rd-person applications and anything you can think of basically.
|
||||
The Notfellchen API serves the purpose of supporting 3rd-person applications, whether you want to display data in a custom format or add data from other sources.
|
||||
|
||||
.. warning::
|
||||
The current API is limited in it's functionality. I you miss a specific feature please contact the developer!
|
||||
The current API is limited in it's functionality. I you miss a specific feature please contact the developers!
|
||||
|
||||
API Access
|
||||
==========
|
||||
@@ -14,17 +14,94 @@ Via browser
|
||||
-----------
|
||||
|
||||
When a user is logged in, they can easily access the API in their browser, authenticated by their session.
|
||||
The API endpoint can be found at /library/api/
|
||||
http://notfellchen.org/
|
||||
|
||||
For example: You can check all current adoption notices here: https://notfellchen.org/api/adoption_notice
|
||||
|
||||
Via token
|
||||
---------
|
||||
|
||||
.. warning::
|
||||
This is currently not supported.
|
||||
|
||||
All users are able to generate a token that allows them to use the API. This can be done in the user's profile.
|
||||
An application can then send this token in the request header for authorization.
|
||||
|
||||
.. code-block::
|
||||
$ curl -X GET http://notfellchen.org/api/adoption_notice -H 'Authorization: Token 49b39856955dc6e5cc04365498d4ad30ea3aed78'
|
||||
|
||||
|
||||
.. warning::
|
||||
Usage or creation of content still has to follow the terms of notfellchen.org.
|
||||
Copyright of content is often held by rescue organizations, so you are not allowed to simply mirror content.
|
||||
Talk to the notfellchen team if you want develop such things.
|
||||
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
|
||||
All Endpoints are documented at https://notfellchen.org/api/schema/swagger-ui/ or at https://notfellchen.org/api/schema/redoc/ if you prefer redoc.
|
||||
The OpenAI schema can be downloaded at https://notfellchen.org/api/schema/
|
||||
|
||||
Examples are documented here.
|
||||
|
||||
Get Adoption Notices
|
||||
++++++++++++++++++++
|
||||
|
||||
.. code-block::
|
||||
|
||||
curl --request GET \
|
||||
--url https://notfellchen.org/api/adoption_notice \
|
||||
--header 'Authorization: {{token}}'
|
||||
|
||||
Create Adoption Notice
|
||||
++++++++++++++++++++++
|
||||
|
||||
.. code-block::
|
||||
|
||||
curl --request POST \
|
||||
--url https://notfellchen.org/api/adoption_notice \
|
||||
--header 'Authorization: {{token}}' \
|
||||
--header 'content-type: multipart/form-data' \
|
||||
--form name=TestAdoption1 \
|
||||
--form searching_since=2024-11-19 \
|
||||
--form 'description=Lorem ipsum **dolor sit** amet' \
|
||||
--form further_information=https://notfellchen.org \
|
||||
--form location_string=Berlin \
|
||||
--form group_only=true
|
||||
|
||||
Add Animal to Adoption Notice
|
||||
+++++++++++++++++++++++++++++
|
||||
|
||||
.. code-block::
|
||||
|
||||
curl --request POST \
|
||||
--url https://notfellchen.org/api/animals/ \
|
||||
--header 'Authorization: {{token}}' \
|
||||
--header 'content-type: multipart/form-data' \
|
||||
--form name=TestAnimal1 \
|
||||
--form date_of_birth=2024-11-19 \
|
||||
--form 'description=Lorem animal **dolor sit**.' \
|
||||
--form sex=F \
|
||||
--form species=1 \
|
||||
--form adoption_notice=1
|
||||
|
||||
Add picture to Animal or Adoption Notice
|
||||
++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
.. code-block::
|
||||
|
||||
curl -X POST https://notfellchen.org/api/images/ \
|
||||
-H "Authorization: Token {{token}}" \
|
||||
-F "image=@256-256-crop.jpg" \
|
||||
-F "alt_text=Puppy enjoying the sunshine" \
|
||||
-F "attach_to_type=animal" \
|
||||
-F "attach_to=48
|
||||
|
||||
Species
|
||||
+++++++
|
||||
|
||||
Getting available species is mainly important when creating animals
|
||||
|
||||
.. code-block::
|
||||
|
||||
curl --request GET \
|
||||
--url https://notfellchen.org/api/species \
|
||||
--header 'Authorization: {{token}}'
|
||||
|
||||
74
docs/_ext/drawio.py
Normal file
@@ -0,0 +1,74 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from docutils import nodes
|
||||
from sphinx.application import Sphinx
|
||||
from sphinx.util.docutils import SphinxDirective
|
||||
from sphinx.util.typing import ExtensionMetadata
|
||||
|
||||
|
||||
class DrawioDirective(SphinxDirective):
|
||||
"""A directive to show a drawio diagram!
|
||||
|
||||
Usage:
|
||||
.. drawio::
|
||||
example-diagram.drawio.html
|
||||
example-diagram.drawio.png
|
||||
:alt: Example of a Draw.io diagram
|
||||
"""
|
||||
|
||||
has_content = False
|
||||
required_arguments = 2 # html and png
|
||||
optional_arguments = 1
|
||||
final_argument_whitespace = True # indicating if the final argument may contain whitespace
|
||||
option_spec = {
|
||||
"alt": str,
|
||||
}
|
||||
|
||||
def run(self) -> list[nodes.Node]:
|
||||
env = self.state.document.settings.env
|
||||
builder = env.app.builder
|
||||
|
||||
# Resolve paths relative to the document
|
||||
docdir = Path(env.doc2path(env.docname)).parent
|
||||
html_rel = Path(self.arguments[0])
|
||||
png_rel = Path(self.arguments[1])
|
||||
html_path = (docdir / html_rel).resolve()
|
||||
png_path = (docdir / png_rel).resolve()
|
||||
|
||||
alt_text = self.options.get("alt", "")
|
||||
|
||||
container = nodes.container()
|
||||
|
||||
# HTML output -> raw HTML node
|
||||
if builder.format == "html":
|
||||
# Embed the HTML file contents directly
|
||||
try:
|
||||
html_content = html_path.read_text(encoding="utf-8")
|
||||
except OSError as e:
|
||||
msg = self.state_machine.reporter.error(f"Cannot read HTML file: {e}")
|
||||
return [msg]
|
||||
aria_attribute = f' aria-label="{alt_text}"' if alt_text else ""
|
||||
raw_html_node = nodes.raw(
|
||||
"",
|
||||
f'<div class="drawio-diagram"{aria_attribute}>{html_content}</div>',
|
||||
format="html",
|
||||
)
|
||||
container += raw_html_node
|
||||
else:
|
||||
# Other outputs -> PNG image node
|
||||
image_node = nodes.image(uri=png_path)
|
||||
container += image_node
|
||||
|
||||
return [container]
|
||||
|
||||
|
||||
def setup(app: Sphinx) -> ExtensionMetadata:
|
||||
app.add_directive("drawio", DrawioDirective)
|
||||
|
||||
return {
|
||||
"version": "0.2",
|
||||
"parallel_read_safe": True,
|
||||
"parallel_write_safe": True,
|
||||
}
|
||||
@@ -67,5 +67,6 @@ Healthchecks
|
||||
You can configure notfellchen to give a hourly ping to a healthchecks server. If this ping is not received, you will get notified and cna check why the celery jobs are no running.
|
||||
Add the following to your `notfellchen.cfg` and adjust the URL to match your check.
|
||||
.. code::
|
||||
|
||||
[monitoring]
|
||||
healthchecks_url=https://health.example.org/ping/5fa7c9b2-753a-4cb3-bcc9-f982f5bc68e8
|
||||
|
||||
12
docs/conf.py
@@ -16,6 +16,10 @@
|
||||
# import sys
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.append(str(Path('_ext').resolve()))
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
@@ -28,7 +32,6 @@ version = ''
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = '0.2.0'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
@@ -40,6 +43,7 @@ release = '0.2.0'
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.ifconfig',
|
||||
'drawio'
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
@@ -69,7 +73,6 @@ exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = None
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
@@ -104,7 +107,6 @@ html_static_path = ['_static']
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'notfellchen'
|
||||
|
||||
|
||||
# -- Options for LaTeX output ------------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
@@ -133,7 +135,6 @@ latex_documents = [
|
||||
'Julian-Samuel Gebühr', 'manual'),
|
||||
]
|
||||
|
||||
|
||||
# -- Options for manual page output ------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
@@ -143,7 +144,6 @@ man_pages = [
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
|
||||
# -- Options for Texinfo output ----------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
@@ -155,7 +155,6 @@ texinfo_documents = [
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
|
||||
# -- Options for Epub output -------------------------------------------------
|
||||
|
||||
# Bibliographic Dublin Core info.
|
||||
@@ -173,5 +172,4 @@ epub_title = project
|
||||
# A list of files that should not be packed into the epub file.
|
||||
epub_exclude_files = ['search.html']
|
||||
|
||||
|
||||
# -- Extension configuration -------------------------------------------------
|
||||
|
||||
@@ -5,7 +5,7 @@ Report a bug
|
||||
^^^^^^^^^^^^
|
||||
|
||||
To report a bug, file an issue on `Github
|
||||
<https://codeberg.org/moanos/notfellchen/issues>`_
|
||||
<https://github.com/moan0s/notfellchen/issues>`_
|
||||
|
||||
Try to include the following information:
|
||||
|
||||
@@ -29,7 +29,7 @@ To contribute simply clone the directory, make your changes and file a
|
||||
pull request.
|
||||
|
||||
If you want to know what can be done, have a look at the current `Github
|
||||
<https://codeberg.org/moanos/notfellchen/issues>`_.
|
||||
<https://github.com/moan0s/notfellchen/issues>`_.
|
||||
|
||||
Get in touch!
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
@@ -5,8 +5,7 @@ What qualifies as release?
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
A new release should be announced when a significant number functions, bugfixes or other improvements to the software
|
||||
is made. Usually this indicates a minor release.
|
||||
Major releases are yet to be determined.
|
||||
is made. Notfellchen follows `Semantic Versioning <https://semver.org/>`_.
|
||||
|
||||
What should be done before a release?
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -14,7 +13,7 @@ What should be done before a release?
|
||||
Tested basic functions
|
||||
######################
|
||||
|
||||
Run :command:`pytest`
|
||||
Run :command:`nf test src`
|
||||
|
||||
Test upgrade on a copy of a production database
|
||||
###############################################
|
||||
@@ -38,4 +37,4 @@ Do a final commit on this change, and tag the commit as release with appropriate
|
||||
git tag -a v1.0.0 -m "Releasing version v1.0.0"
|
||||
git push origin v1.0.0
|
||||
|
||||
Make sure the tag is visible on Codeberg and celebrate 🥳
|
||||
Make sure the tag is visible on GitHub/Codeberg and celebrate 🥳
|
||||
|
||||
BIN
docs/user/Screenshot-Moderationstools.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
docs/user/Screenshot-hilfreiche-Links.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
11
docs/user/Tiere-in-Vermittlung-entdecken.drawio.html
Normal file
BIN
docs/user/Tiere-in-Vermittlung-entdecken.drawio.png
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
docs/user/Vermittlung-Lifecycle.drawio.png
Normal file
|
After Width: | Height: | Size: 150 KiB |
11
docs/user/Vermittlung_Lifecycle.drawio.html
Normal file
@@ -6,14 +6,27 @@ Jede Vermittlung kann abonniert werden. Dafür klickst du auf die Glocke neben d
|
||||
|
||||
.. image:: abonnieren.png
|
||||
|
||||
|
||||
Einstellungen
|
||||
-------------
|
||||
|
||||
Du kannst E-Mail Benachrichtigungen in den Einstellungen deaktivieren.
|
||||
|
||||
.. image::
|
||||
einstellungen-benachrichtigungen.png
|
||||
:alt: Screenshot der Profileinstellungen in Notfellchen. Ein roter Pfeil zeigt auf einen Schalter "E-Mail Benachrichtigungen"
|
||||
|
||||
Auf der Website
|
||||
+++++++++++++++
|
||||
|
||||
.. image::
|
||||
screenshot-benachrichtigungen.png
|
||||
:alt: Screenshot der Menüleiste von Notfellchen.org. Neben dem Symbol einer Glocke steht die Zahl 27.
|
||||
|
||||
|
||||
|
||||
E-Mail
|
||||
++++++
|
||||
|
||||
Mit während deiner :doc:`registrierung` gibst du eine E-Mail Addresse an.
|
||||
|
||||
Benachrichtigungen senden wir per Mail - du kannst das jederzeit in den Einstellungen deaktivieren.
|
||||
Mit während deiner :doc:`registrierung` gibst du eine E-Mail Adresse an. An diese senden wir Benachrichtigungen, außer
|
||||
du deaktiviert dies wie oben beschrieben.
|
||||
BIN
docs/user/einstellungen-benachrichtigungen.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
58
docs/user/erste-schritte.rst
Normal file
@@ -0,0 +1,58 @@
|
||||
Erste Schritte
|
||||
==============
|
||||
|
||||
Tiere zum Adoptieren suchen
|
||||
---------------------------
|
||||
|
||||
Wenn du Tiere zum adoptieren suchst, brauchst du keinen Account. Du kannst bequem die `Suche <https://notfellchen.org/suchen/>`_ nutzen, um Tiere zur Adoption in deiner Nähe zu finden.
|
||||
Wenn dich eine Vermittlung interessiert, kannst du folgendes tun
|
||||
|
||||
* die Vermittlung aufrufen um Details zu sehen
|
||||
* den Link :guilabel:`Weitere Informationen` anklicken um auf der Tierheimwebsite mehr zu erfahren
|
||||
* per Kommentar weitere Informationen erfragen oder hinzufügen
|
||||
|
||||
Wenn du die Tiere tatsächlich informieren willst, folge der Anleitung unter :guilabel:`Adoptionsprozess`.
|
||||
Dieser kann sich je nach Tierschutzorganisation unterscheiden.
|
||||
|
||||
.. image::
|
||||
screenshot-adoptionsprozess.png
|
||||
:alt: Screenshot der Sektion "Adoptionsprozess" einer Vermittlungsanzeige. Der Prozess ist folgendermaßen: 1. Link zu "Weiteren Informationen" prüfen, 2. Organization kontaktieren, 3. Bei erfolgreicher Vermittlung: Vermittlung als geschlossen melden
|
||||
|
||||
Suchen abonnieren
|
||||
+++++++++++++++++
|
||||
|
||||
Es kann sein, dass es in deiner Umgebung keine passenden Tiere für deine Suche gibt. Damit du nicht ständig wieder Suchen musst, gibt es die Funktion "Suche abonnieren".
|
||||
Wenn du eine Suche abonnierst, wirst du für neue Vermittlungen, die den Kriterien der Suche entsprechen, benachrichtigt.
|
||||
|
||||
.. image::
|
||||
screenshot-suche-abonnieren.png
|
||||
:alt: Screenshot der Suchmaske auf Notfellchen.org . Ein roter Pfeil zeigt auf den Button "Suche abonnieren"
|
||||
|
||||
.. important::
|
||||
|
||||
Um Suchen zu abonnieren brauchst du einen Account. Wie du einen Account erstellst erfährst du hier: :doc:`registrierung`.
|
||||
|
||||
.. hint::
|
||||
|
||||
Mehr über Benachrichtigungen findest du hier: :doc:`benachrichtigungen`.
|
||||
|
||||
Vermittlungen hinzufügen
|
||||
------------------------
|
||||
|
||||
Gehe zu `Vermittlung hinzufügen <https://notfellchen.org/vermitteln/>`_ um eine neue Vermittlung einzustellen.
|
||||
Füge alle Informationen die du hast hinzu.
|
||||
|
||||
.. important::
|
||||
|
||||
Um Vermittlungen hinzuzufügen brauchst du einen Account.
|
||||
Wie du einen Account erstellst erfährst du hier: :doc:`registrierung`.
|
||||
|
||||
|
||||
.. important::
|
||||
|
||||
Vermittlungen die du einstellst müssen erst durch Moderator\*innen freigeschaltet werden. Das passiert normalerweise
|
||||
innerhalb von 24 Stunden. Wenn deine Vermittlung dann noch nicht freigeschaltet ist, prüfe bitte dein E-Mail Postfach,
|
||||
es könnte sein, dass die Moderator\*innen Rückfragen haben. Melde dich gerne unter info@notfellchen.org, wenn deine
|
||||
Vermittlung nach 24 Stunden nicht freigeschaltet ist.
|
||||
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
******************
|
||||
User Dokumentation
|
||||
******************
|
||||
****************
|
||||
Benutzerhandbuch
|
||||
****************
|
||||
|
||||
Im Benutzerhandbuch findest du Informationen zur Benutzung von `notfellchen.org <https://notfellchen.org>`_.
|
||||
Solltest du darüber hinaus Fragen haben, komm gerne auf uns zu: info@notfellchen.org
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Inhalt:
|
||||
|
||||
|
||||
erste-schritte.rst
|
||||
registrierung.rst
|
||||
vermittlungen.rst
|
||||
moderationskonzept.rst
|
||||
benachrichtigungen.rst
|
||||
organisationen-pruefen.rst
|
||||
|
||||
55
docs/user/organisationen-pruefen.rst
Normal file
@@ -0,0 +1,55 @@
|
||||
Tiere in Vermittlung systematisch entdecken & eintragen
|
||||
=======================================================
|
||||
|
||||
Notfellchen hat eine Liste der meisten deutschen Tierheime und anderer Tierschutzorganisationen.
|
||||
Die meisten dieser Organisationen nehmen Tiere auf die bei Notfellchen eingetragen werden können.
|
||||
Es ist daher das Ziel, diese Organisationen alle zwei Wochen auf neue Tiere zu prüfen.
|
||||
|
||||
|
||||
+-------------------------------------------------+---------+----------------------+
|
||||
| Gruppe | Anzahl | Zuletzt aktualisiert |
|
||||
+=================================================+=========+======================+
|
||||
| Tierschutzorganisationen im Verzeichnis | 550 | Oktober 2025 |
|
||||
+-------------------------------------------------+---------+----------------------+
|
||||
| Tierschutzorganisationen in regelmäßigerPrüfung | 412 | Oktober 2025 |
|
||||
+-------------------------------------------------+---------+----------------------+
|
||||
|
||||
.. warning::
|
||||
|
||||
Organisationen auf neue Tiere zu prüfen ist eine Funktion für Moderator\*innen. Falls du Lust hast mitzuhelfen,
|
||||
meld dich unter info@notfellchen.org
|
||||
|
||||
Als Moderator\*in kannst du direkt auf den `Moderations-Check <https://notfellchen.org/organization-check/>`_ zugreifen
|
||||
oder findest ihn in unter :menuselection:`Hilfreiche Links --> Moderationstools`:
|
||||
|
||||
.. image::
|
||||
Screenshot-hilfreiche-Links.png
|
||||
:alt: Screenshot der Hilfreichen Links. Zur Auswahl stehen "Tierheime in der Nähe","Moderationstools" und "Admin-Bereich"
|
||||
|
||||
.. image::
|
||||
Screenshot-Moderationstools.png
|
||||
:alt: Screenshot der Moderationstools. Zur Auswahl stehen "Moderationswarteschlange", "Up-to-Date Check", "Organisations-Check" und "Vermittlung ins Fediverse posten".
|
||||
|
||||
|
||||
Arbeitsmodus
|
||||
------------
|
||||
|
||||
.. drawio::
|
||||
Tiere-in-Vermittlung-entdecken.drawio.html
|
||||
Tiere-in-Vermittlung-entdecken.drawio.png
|
||||
|
||||
Shortcuts
|
||||
---------
|
||||
|
||||
Um die Prüfung schneller zu gestalten, gibt es eine Reihe von Shortcuts die du nutzen kannst. Aus Gründen der
|
||||
Übersichtlichkeit sind im Folgenden auch Shortcuts im Browser aufgeführt.
|
||||
|
||||
+------------------------------------------------------+---------------+
|
||||
| Aktion | Shortcut |
|
||||
+======================================================+===============+
|
||||
| Website der ersten Tierschutzorganisation öffnen | :kbd:`O` |
|
||||
+------------------------------------------------------+---------------+
|
||||
| Tab schließen (Firefox/Chrome) | :kbd:`STRG+W` |
|
||||
+------------------------------------------------------+---------------+
|
||||
| Erste Tierschutzorganisationa als geprüft markieren | :kbd:`C` |
|
||||
+------------------------------------------------------+---------------+
|
||||
BIN
docs/user/screenshot-adoptionsprozess.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
docs/user/screenshot-benachrichtigungen.png
Normal file
|
After Width: | Height: | Size: 205 KiB |
BIN
docs/user/screenshot-suche-abonnieren.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
docs/user/screenshot-suche.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
@@ -1,7 +1,7 @@
|
||||
Vermittlungen
|
||||
=============
|
||||
|
||||
Vermittlungen können von allen Nutzer*innen mit Account erstellt werden. Vermittlungen normaler Nutzer*innen kommen dann in eine Warteschlange und werden vom Admin & Modertionsteam geprüft und sichtbar geschaltet.
|
||||
Vermittlungen können von allen Nutzer\*innen mit Account erstellt werden. Vermittlungen normaler Nutzer*innen kommen dann in eine Warteschlange und werden vom Admin & Modertionsteam geprüft und sichtbar geschaltet.
|
||||
Tierheime und Pflegestellen können auf Anfrage einen Koordinations-Status bekommen, wodurch sie Vermittlungsanzeigen erstellen können die direkt öffentlich sichtbar sind.
|
||||
|
||||
Jede Vermittlung hat ein "Zuletzt-geprüft" Datum, das anzeigt, wann ein Mensch zuletzt überprüft hat, ob die Anzeige noch aktuell ist.
|
||||
@@ -15,3 +15,114 @@ Die Kommentarfunktion von Vermittlungen ermöglicht es angemeldeten Nutzer*innen
|
||||
Ersteller*innen von Vermittlungen werden über neue Kommentare per Mail benachrichtigt, ebenso alle die die Vermittlung abonniert haben.
|
||||
|
||||
Kommentare können, wie Vermittlungen, gemeldet werden.
|
||||
|
||||
.. drawio::
|
||||
Vermittlung_Lifecycle.drawio.html
|
||||
Vermittlung-Lifecycle.drawio.png
|
||||
:alt: Diagramm das den Prozess der Vermittlungen zeigt.
|
||||
|
||||
|
||||
Adoption Notice Status Choices
|
||||
++++++++++++++++++++++++++++++
|
||||
|
||||
Aktiv
|
||||
-----
|
||||
|
||||
Aktive Vermittlungen die über die Suche auffindbar sind.
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
:width: 100%
|
||||
:widths: 1 1 2
|
||||
|
||||
* - Value
|
||||
- Label
|
||||
- Description
|
||||
|
||||
* - ``active_searching``
|
||||
- Searching
|
||||
-
|
||||
|
||||
* - ``active_interested``
|
||||
- Interested
|
||||
- Jemand hat bereits Interesse an den Tieren.
|
||||
|
||||
Warte auf Aktion
|
||||
----------------
|
||||
|
||||
Vermittlungen in diesem Status warten darauf, dass ein Mensch sie überprüft. Sie können nicht über die Suche gefunden werden.
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
:width: 100%
|
||||
:widths: 1 1 2
|
||||
|
||||
* - ``awaiting_action_waiting_for_review``
|
||||
- Waiting for review
|
||||
- Neue Vermittlung die deaktiviert ist bis Moderator*innen sie überprüfen.
|
||||
|
||||
* - ``awaiting_action_needs_additional_info``
|
||||
- Needs additional info
|
||||
- Deaktiviert bis Informationen nachgetragen werden.
|
||||
|
||||
* - ``disabled_unchecked``
|
||||
- Unchecked
|
||||
- Vermittlung deaktiviert bis sie vom Team auf Aktualität geprüft wurde.
|
||||
|
||||
Geschlossen
|
||||
-----------
|
||||
|
||||
Geschlossene Vermittlungen tauchen in keiner Suche auf. Sie werden aber weiterhin angezeigt, wenn der Link zu ihnen direkt aufgerufen wird.
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
:width: 100%
|
||||
:widths: 1 1 2
|
||||
|
||||
* - ``closed_successful_with_notfellchen``
|
||||
- Successful (with Notfellchen)
|
||||
- Vermittlung erfolgreich abgeschlossen.
|
||||
|
||||
* - ``closed_successful_without_notfellchen``
|
||||
- Successful (without Notfellchen)
|
||||
- Vermittlung erfolgreich abgeschlossen.
|
||||
|
||||
* - ``closed_animal_died``
|
||||
- Animal died
|
||||
- Die zu vermittelnden Tiere sind über die Regenbrücke gegangen.
|
||||
|
||||
* - ``closed_for_other_adoption_notice``
|
||||
- Closed for other adoption notice
|
||||
- Vermittlung wurde zugunsten einer anderen geschlossen.
|
||||
|
||||
* - ``closed_not_open_for_adoption_anymore``
|
||||
- Not open for adoption anymore
|
||||
- Tier(e) stehen nicht mehr zur Vermittlung bereit.
|
||||
|
||||
* - ``closed_link_to_more_info_not_reachable``
|
||||
- Der Link zu weiteren Informationen ist nicht mehr erreichbar.
|
||||
- Der Link zu weiteren Informationen ist nicht mehr erreichbar, die Vermittlung wurde daher automatisch deaktiviert.
|
||||
|
||||
* - ``closed_other``
|
||||
- Other (closed)
|
||||
- Vermittlung geschlossen.
|
||||
|
||||
Deaktiviert
|
||||
-----------
|
||||
|
||||
Deaktivierte Vermittlungen werden nur noch Moderator\*innen und Administrator\*innen angezeigt.
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
:width: 100%
|
||||
:widths: 1 1 2
|
||||
|
||||
* - ``disabled_against_the_rules``
|
||||
- Against the rules
|
||||
- Vermittlung deaktiviert da sie gegen die Regeln verstößt.
|
||||
|
||||
* - ``disabled_other``
|
||||
- Other (disabled)
|
||||
- Vermittlung deaktiviert.
|
||||
|
||||
|
||||
|
||||
@@ -18,10 +18,16 @@ media=./media
|
||||
static=./static
|
||||
|
||||
[mail]
|
||||
console-only=true
|
||||
console_only=true
|
||||
|
||||
[logging]
|
||||
app_log_level=INFO
|
||||
django_log_level=INFO
|
||||
|
||||
[geocoding]
|
||||
api_url=https://photon.hyteck.de/api
|
||||
api_format=photon
|
||||
|
||||
[security]
|
||||
totp_issuer="NF Localhost"
|
||||
webauth_allow_insecure_origin=True
|
||||
|
||||
497
package-lock.json
generated
Normal file
@@ -0,0 +1,497 @@
|
||||
{
|
||||
"name": "notfellchen",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"bulma": "^1.0.4",
|
||||
"sass": "^1.89.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
|
||||
"integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"detect-libc": "^1.0.3",
|
||||
"is-glob": "^4.0.3",
|
||||
"micromatch": "^4.0.5",
|
||||
"node-addon-api": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@parcel/watcher-android-arm64": "2.5.1",
|
||||
"@parcel/watcher-darwin-arm64": "2.5.1",
|
||||
"@parcel/watcher-darwin-x64": "2.5.1",
|
||||
"@parcel/watcher-freebsd-x64": "2.5.1",
|
||||
"@parcel/watcher-linux-arm-glibc": "2.5.1",
|
||||
"@parcel/watcher-linux-arm-musl": "2.5.1",
|
||||
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
|
||||
"@parcel/watcher-linux-arm64-musl": "2.5.1",
|
||||
"@parcel/watcher-linux-x64-glibc": "2.5.1",
|
||||
"@parcel/watcher-linux-x64-musl": "2.5.1",
|
||||
"@parcel/watcher-win32-arm64": "2.5.1",
|
||||
"@parcel/watcher-win32-ia32": "2.5.1",
|
||||
"@parcel/watcher-win32-x64": "2.5.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-android-arm64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
|
||||
"integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-arm64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
|
||||
"integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-x64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
|
||||
"integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-freebsd-x64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
|
||||
"integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm-glibc": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
|
||||
"integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm-musl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
|
||||
"integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-glibc": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
|
||||
"integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-musl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
|
||||
"integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-glibc": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
|
||||
"integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-musl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
|
||||
"integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-arm64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
|
||||
"integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-ia32": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
|
||||
"integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-x64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
|
||||
"integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/bulma": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/bulma/-/bulma-1.0.4.tgz",
|
||||
"integrity": "sha512-Ffb6YGXDiZYX3cqvSbHWqQ8+LkX6tVoTcZuVB3lm93sbAVXlO0D6QlOTMnV6g18gILpAXqkG2z9hf9z4hCjz2g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.16.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"detect-libc": "bin/detect-libc.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/immutable": {
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz",
|
||||
"integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-glob": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"is-extglob": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"braces": "^3.0.3",
|
||||
"picomatch": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.89.2",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.89.2.tgz",
|
||||
"integrity": "sha512-xCmtksBKd/jdJ9Bt9p7nPKiuqrlBMBuuGkQlkhZjjQk3Ty48lv93k5Dq6OPkKt4XwxDJ7tvlfrTa1MPA9bf+QA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.0",
|
||||
"immutable": "^5.0.2",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"sass": "sass.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@parcel/watcher": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
package.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"bulma": "^1.0.4",
|
||||
"sass": "^1.89.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build-bulma": "sass --load-path=node_modules src/fellchensammlung/static/fellchensammlung/css/main.scss src/fellchensammlung/static/fellchensammlung/css/main.css --style compressed",
|
||||
"start": "npm run build-bulma -- --watch"
|
||||
}
|
||||
}
|
||||
@@ -6,15 +6,15 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "notfellchen"
|
||||
description = "A tool to help."
|
||||
description = "A website to help animals to find a loving home. It features organized input of adoption notices and related animals including automated lifecycle, location-based search, roles, and support for easy checking of rescue organizations."
|
||||
authors = [
|
||||
{name = "moanos", email = "julian-samuel@gebuehr.net"},
|
||||
{ name = "moanos", email = "julian-samuel@gebuehr.net" },
|
||||
]
|
||||
maintainers = [
|
||||
{name = "moanos", email = "julian-samuel@gebuehr.net"},
|
||||
{ name = "moanos", email = "julian-samuel@gebuehr.net" },
|
||||
]
|
||||
keywords = ["animal", "adoption", "django", "rescue", ]
|
||||
license = {text = "AGPL-3.0-or-later"}
|
||||
keywords = ["animal", "adoption", "django", "rescue", "rats" ]
|
||||
license = { text = "AGPL-3.0-or-later" }
|
||||
classifiers = [
|
||||
"Environment :: Web",
|
||||
"License :: OSI Approved :: GNU Affero General Public License v3",
|
||||
@@ -24,29 +24,36 @@ classifiers = [
|
||||
]
|
||||
dependencies = [
|
||||
"Django",
|
||||
"coverage",
|
||||
"codecov",
|
||||
"sphinx",
|
||||
"sphinx-rtd-theme",
|
||||
"gunicorn",
|
||||
"fontawesomefree",
|
||||
"whitenoise",
|
||||
"model_bakery",
|
||||
"markdown",
|
||||
"Pillow",
|
||||
"django-registration",
|
||||
"psycopg2",
|
||||
"psycopg2-binary",
|
||||
"django-crispy-forms",
|
||||
"crispy-bootstrap4",
|
||||
"djangorestframework",
|
||||
"celery[redis]"
|
||||
"celery[redis]",
|
||||
"drf-spectacular[sidecar]",
|
||||
"django-widget-tweaks",
|
||||
"django-super-deduper",
|
||||
"django-allauth[mfa]"
|
||||
]
|
||||
|
||||
dynamic = ["version", "readme"]
|
||||
|
||||
[project.optional-dependencies]
|
||||
develop = [
|
||||
"pytest",
|
||||
"pytest",
|
||||
"coverage",
|
||||
"model_bakery",
|
||||
]
|
||||
docs = [
|
||||
"sphinx",
|
||||
"sphinx-rtd-theme",
|
||||
"sphinx-autobuild"
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
@@ -61,6 +68,6 @@ nf = 'notfellchen.main:main'
|
||||
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
version = {attr = "notfellchen.__version__"}
|
||||
readme = {file = "README.md"}
|
||||
version = { attr = "notfellchen.__version__" }
|
||||
readme = { file = "README.md" }
|
||||
|
||||
|
||||
275
scripts/upload_animal_shelters.py
Normal file
@@ -0,0 +1,275 @@
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from types import SimpleNamespace
|
||||
|
||||
import requests
|
||||
# TODO: consider using OSMPythonTools instead of requests or overpass library
|
||||
from osmtogeojson import osmtogeojson
|
||||
from tqdm import tqdm
|
||||
|
||||
DEFAULT_OSM_DATA_FILE = "export.geojson"
|
||||
# Search area must be the official name, e.g. "Germany" is not a valid area name in Overpass API
|
||||
# Consider instead finding & using the code within the query itself, e.g. "ISO3166-1"="DE"
|
||||
DEFAULT_OVERPASS_SEARCH_AREA = "Deutschland"
|
||||
|
||||
|
||||
def parse_args():
|
||||
"""Parse command-line arguments."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Download animal shelter data from the Overpass API to the Notfellchen API.")
|
||||
parser.add_argument("--api-token", type=str, help="API token for authentication.")
|
||||
parser.add_argument("--area", type=str, help="Area to search for animal shelters (default: Deutschland).")
|
||||
parser.add_argument("--instance", type=str, help="API instance URL.")
|
||||
parser.add_argument("--data-file", type=str, help="Path to the GeoJSON file containing (only) animal shelters.")
|
||||
parser.add_argument("--use-cached", action='store_true', help="Use the stored GeoJSON file")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def get_config():
|
||||
"""Get configuration from environment variables or command-line arguments."""
|
||||
args = parse_args()
|
||||
|
||||
api_token = args.api_token or os.getenv("NOTFELLCHEN_API_TOKEN")
|
||||
# TODO: document new environment variable NOTFELLCHEN_AREA
|
||||
area = args.area or os.getenv("NOTFELLCHEN_AREA", DEFAULT_OVERPASS_SEARCH_AREA)
|
||||
instance = args.instance or os.getenv("NOTFELLCHEN_INSTANCE")
|
||||
data_file = args.data_file or os.getenv("NOTFELLCHEN_DATA_FILE", DEFAULT_OSM_DATA_FILE)
|
||||
use_cached = args.use_cached or os.getenv("NOTFELLCHEN_USE_CACHED", False)
|
||||
|
||||
if not api_token or not instance:
|
||||
raise ValueError("API token and instance URL must be provided via environment variables or CLI arguments.")
|
||||
|
||||
return api_token, area, instance, data_file, use_cached
|
||||
|
||||
|
||||
def get_or_none(data, key):
|
||||
if key in data["properties"].keys():
|
||||
return data["properties"][key]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def get_or_empty(data, key):
|
||||
if key in data["properties"].keys():
|
||||
return data["properties"][key]
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
def choose(keys, data, replace=False):
|
||||
for key in keys:
|
||||
if key in data.keys():
|
||||
if replace:
|
||||
return data[key].replace(" ", "").replace("-", "").replace("(", "").replace(")", "")
|
||||
else:
|
||||
return data[key]
|
||||
return None
|
||||
|
||||
|
||||
def add(value, platform):
|
||||
if value != "":
|
||||
if value.find(platform) == -1:
|
||||
return f"https://www.{platform}.com/{value}"
|
||||
else:
|
||||
return value
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def https(value):
|
||||
if value is not None and value != "":
|
||||
value = value.replace("http://", "")
|
||||
if value.find("https") == -1:
|
||||
return f"https://{value}"
|
||||
else:
|
||||
return value
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def calc_coordinate_center(coordinates):
|
||||
"""
|
||||
Calculates the center as the arithmetic mean of the list of coordinates.
|
||||
|
||||
Not perfect because earth is a sphere (citation needed) but good enough.
|
||||
"""
|
||||
if not coordinates:
|
||||
return None, None
|
||||
|
||||
lon_sum = 0.0
|
||||
lat_sum = 0.0
|
||||
count = 0
|
||||
|
||||
for lon, lat in coordinates:
|
||||
lon_sum += lon
|
||||
lat_sum += lat
|
||||
count += 1
|
||||
|
||||
return lon_sum / count, lat_sum / count
|
||||
|
||||
|
||||
def get_center_coordinates(geometry):
|
||||
"""
|
||||
Given a GeoJSON geometry dict, return (longitude, latitude)
|
||||
|
||||
If a shape, calculate the center, else reurn the point
|
||||
"""
|
||||
geom_type = geometry["type"]
|
||||
coordinates = geometry["coordinates"]
|
||||
|
||||
if geom_type == "Point":
|
||||
return coordinates[0], coordinates[1]
|
||||
|
||||
elif geom_type == "LineString":
|
||||
return calc_coordinate_center(coordinates)
|
||||
|
||||
elif geom_type == "Polygon":
|
||||
outer_ring = coordinates[0]
|
||||
return calc_coordinate_center(outer_ring)
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unsupported geometry type: {geom_type}")
|
||||
|
||||
|
||||
# TODO: take note of new get_overpass_result function which does the bulk of the new overpass query work
|
||||
def get_overpass_result(area, data_file):
|
||||
"""Build the Overpass query for fetching animal shelters in the specified area."""
|
||||
overpass_endpoint = "https://overpass-api.de/api/interpreter"
|
||||
overpass_query = f"""
|
||||
[out:json][timeout:25];
|
||||
area[name="{area}"]->.searchArea;
|
||||
nwr["amenity"="animal_shelter"](area.searchArea);
|
||||
out body;
|
||||
>;
|
||||
out skel qt;
|
||||
"""
|
||||
r = requests.get(overpass_endpoint, params={'data': overpass_query})
|
||||
if r.status_code == 200:
|
||||
rjson = r.json()
|
||||
result = osmtogeojson.process_osm_json(rjson)
|
||||
with open(data_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(result, f, ensure_ascii=False)
|
||||
return result
|
||||
|
||||
|
||||
def add_if_available(base_data, keys, result):
|
||||
# Loads the data into the org if available
|
||||
for key in keys:
|
||||
if getattr(base_data, key) is not None:
|
||||
result[key] = getattr(base_data, key)
|
||||
return result
|
||||
|
||||
|
||||
def create_location(tierheim, instance, headers):
|
||||
location_data = {
|
||||
"place_id": tierheim["id"],
|
||||
"longitude": get_center_coordinates(tierheim["geometry"])[0],
|
||||
"latitude": get_center_coordinates(tierheim["geometry"])[1],
|
||||
"name": tierheim["properties"]["name"],
|
||||
"city": tierheim["properties"]["addr:city"],
|
||||
"housenumber": get_or_empty(tierheim, "addr:housenumber"),
|
||||
"postcode": get_or_empty(tierheim, "addr:postcode"),
|
||||
"street": get_or_empty(tierheim, "addr:street"),
|
||||
"countrycode": get_or_empty(tierheim, "addr:country"),
|
||||
}
|
||||
|
||||
location_result = requests.post(f"{instance}/api/locations/", json=location_data, headers=headers)
|
||||
|
||||
if location_result.status_code != 201:
|
||||
try:
|
||||
print(
|
||||
f"Location for {tierheim["properties"]["name"]}:{location_result.status_code} {location_result.json()} not created")
|
||||
except requests.exceptions.JSONDecodeError:
|
||||
print(f"Location for {tierheim["properties"]["name"]} could not be created")
|
||||
exit()
|
||||
|
||||
return location_result.json()
|
||||
|
||||
|
||||
def main():
|
||||
api_token, area, instance, data_file, use_cached = get_config()
|
||||
if not use_cached:
|
||||
# Query shelters
|
||||
overpass_result = get_overpass_result(area, data_file)
|
||||
if overpass_result is None:
|
||||
print("Error: get_overpass_result returned None")
|
||||
return
|
||||
else:
|
||||
with open(data_file, 'r', encoding='utf-8') as f:
|
||||
overpass_result = json.load(f)
|
||||
|
||||
# Set headers and endpoint
|
||||
endpoint = f"{instance}/api/organizations/"
|
||||
h = {'Authorization': f'Token {api_token}', "content-type": "application/json"}
|
||||
|
||||
tierheime = overpass_result["features"]
|
||||
stats = {"num_updated_orgs": 0,
|
||||
"num_inserted_orgs": 0}
|
||||
|
||||
for idx, tierheim in enumerate(tqdm(tierheime)):
|
||||
# Check if data is low quality
|
||||
if "name" not in tierheim["properties"].keys() or "addr:city" not in tierheim["properties"].keys():
|
||||
continue
|
||||
|
||||
# Load TH data in for easier accessing
|
||||
th_data = SimpleNamespace(
|
||||
name=tierheim["properties"]["name"],
|
||||
email=choose(("contact:email", "email"), tierheim["properties"]),
|
||||
phone_number=choose(("contact:phone", "phone"), tierheim["properties"], replace=True),
|
||||
fediverse_profile=get_or_none(tierheim, "contact:mastodon"),
|
||||
facebook=https(add(get_or_empty(tierheim, "contact:facebook"), "facebook")),
|
||||
instagram=https(add(get_or_empty(tierheim, "contact:instagram"), "instagram")),
|
||||
website=https(choose(("contact:website", "website"), tierheim["properties"])),
|
||||
description=get_or_none(tierheim, "opening_hours"),
|
||||
external_object_identifier=tierheim["id"],
|
||||
EXTERNAL_SOURCE_IDENTIFIER="OSM",
|
||||
)
|
||||
|
||||
# Define here for later
|
||||
optional_data = ["email", "phone_number", "website", "description", "fediverse_profile", "facebook",
|
||||
"instagram"]
|
||||
|
||||
# Check if rescue organization exists
|
||||
search_data = {"external_source_identifier": "OSM",
|
||||
"external_object_identifier": f"{tierheim["id"]}"}
|
||||
search_result = requests.get(f"{instance}/api/organizations", params=search_data, headers=h)
|
||||
# Rescue organization exits
|
||||
if search_result.status_code == 200:
|
||||
stats["num_updated_orgs"] += 1
|
||||
org_id = search_result.json()[0]["id"]
|
||||
logging.debug(f"{th_data.name} already exists as ID {org_id}.")
|
||||
org_patch_data = {"id": org_id,
|
||||
"name": th_data.name}
|
||||
if search_result.json()[0]["location"] is None:
|
||||
location = create_location(tierheim, instance, h)
|
||||
org_patch_data["location"] = location["id"]
|
||||
|
||||
org_patch_data = add_if_available(th_data, optional_data, org_patch_data)
|
||||
|
||||
result = requests.patch(endpoint, json=org_patch_data, headers=h)
|
||||
if result.status_code != 200:
|
||||
logging.warning(f"Updating {tierheim['properties']['name']} failed:{result.status_code} {result.json()}")
|
||||
continue
|
||||
# Rescue organization does not exist
|
||||
else:
|
||||
stats["num_inserted_orgs"] += 1
|
||||
location = create_location(tierheim, instance, h)
|
||||
org_data = {"name": tierheim["properties"]["name"],
|
||||
"external_object_identifier": f"{tierheim["id"]}",
|
||||
"external_source_identifier": "OSM",
|
||||
"location": location["id"]
|
||||
}
|
||||
|
||||
org_data = add_if_available(th_data, optional_data, org_data)
|
||||
|
||||
result = requests.post(endpoint, json=org_data, headers=h)
|
||||
|
||||
if result.status_code != 201:
|
||||
print(f"{idx} {tierheim["properties"]["name"]}:{result.status_code} {result.json()}")
|
||||
print(f"Upload finished. Inserted {stats['num_inserted_orgs']} new orgs and updated {stats['num_updated_orgs']} orgs.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,27 +1,68 @@
|
||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||
from django.contrib import admin
|
||||
from django.utils.html import format_html
|
||||
import csv
|
||||
|
||||
from .models import User, Language, Text, ReportComment, ReportAdoptionNotice, Log, Timestamp
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin import EmptyFieldListFilter
|
||||
from django.http import HttpResponse
|
||||
from django.utils.html import format_html
|
||||
from django.urls import reverse
|
||||
from django.utils.http import urlencode
|
||||
|
||||
from .models import Language, Text, ReportComment, ReportAdoptionNotice, Log, Timestamp, SearchSubscription, \
|
||||
SpeciesSpecificURL, ImportantLocation, SocialMediaPost
|
||||
|
||||
from .models import Animal, Species, RescueOrganization, AdoptionNotice, Location, Rule, Image, ModerationAction, \
|
||||
Comment, Report, Announcement, AdoptionNoticeStatus, User, Subscriptions
|
||||
Comment, Announcement, User, Subscriptions, Notification
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class StatusInline(admin.StackedInline):
|
||||
model = AdoptionNoticeStatus
|
||||
from .tools.model_helpers import AdoptionNoticeStatusChoices
|
||||
|
||||
|
||||
@admin.register(AdoptionNotice)
|
||||
class AdoptionNoticeAdmin(admin.ModelAdmin):
|
||||
search_fields = ("name__icontains", "description__icontains")
|
||||
inlines = [
|
||||
StatusInline,
|
||||
]
|
||||
list_filter = ("owner",)
|
||||
actions = ("activate",)
|
||||
|
||||
def activate(self, request, queryset):
|
||||
for obj in queryset:
|
||||
obj.adoption_notice_status = AdoptionNoticeStatusChoices.Active.SEARCHING
|
||||
obj.save()
|
||||
|
||||
activate.short_description = _("Ausgewählte Vermittlungen aktivieren")
|
||||
|
||||
|
||||
# Re-register UserAdmin
|
||||
admin.site.register(User)
|
||||
@admin.register(User)
|
||||
class UserAdmin(admin.ModelAdmin):
|
||||
search_fields = ("usernamname__icontains", "first_name__icontains", "last_name__icontains", "email__icontains")
|
||||
list_display = ("username", "email", "trust_level", "is_active", "view_adoption_notices")
|
||||
list_filter = ("is_active", "trust_level",)
|
||||
actions = ("export_as_csv",)
|
||||
|
||||
def view_adoption_notices(self, obj):
|
||||
count = obj.adoption_notices.count()
|
||||
url = (
|
||||
reverse("admin:fellchensammlung_adoptionnotice_changelist")
|
||||
+ "?"
|
||||
+ urlencode({"owner__id": f"{obj.id}"})
|
||||
)
|
||||
return format_html('<a href="{}">{} Adoption Notices</a>', url, count)
|
||||
|
||||
def export_as_csv(self, request, queryset):
|
||||
meta = self.model._meta
|
||||
field_names = [field.name for field in meta.fields]
|
||||
|
||||
response = HttpResponse(content_type='text/csv')
|
||||
response['Content-Disposition'] = 'attachment; filename={}.csv'.format(meta)
|
||||
writer = csv.writer(response)
|
||||
|
||||
writer.writerow(field_names)
|
||||
for obj in queryset:
|
||||
row = writer.writerow([getattr(obj, field) for field in field_names])
|
||||
|
||||
return response
|
||||
|
||||
export_as_csv.short_description = _("Ausgewählte User exportieren")
|
||||
|
||||
|
||||
def _reported_content_link(obj):
|
||||
@@ -51,11 +92,19 @@ class ReportAdoptionNoticeAdmin(admin.ModelAdmin):
|
||||
reported_content_link.short_description = "Reported Content"
|
||||
|
||||
|
||||
class SpeciesSpecificURLInline(admin.StackedInline):
|
||||
model = SpeciesSpecificURL
|
||||
|
||||
|
||||
@admin.register(RescueOrganization)
|
||||
class RescueOrganizationAdmin(admin.ModelAdmin):
|
||||
search_fields = ("name__icontains",)
|
||||
search_fields = ("name", "description", "internal_comment", "location_string", "location__city")
|
||||
list_display = ("name", "trusted", "allows_using_materials", "website")
|
||||
list_filter = ("allows_using_materials", "trusted",)
|
||||
list_filter = ("allows_using_materials", "trusted", ("external_source_identifier", EmptyFieldListFilter))
|
||||
|
||||
inlines = [
|
||||
SpeciesSpecificURLInline,
|
||||
]
|
||||
|
||||
|
||||
@admin.register(Text)
|
||||
@@ -63,15 +112,65 @@ class TextAdmin(admin.ModelAdmin):
|
||||
search_fields = ("title__icontains", "text_code__icontains",)
|
||||
|
||||
|
||||
@admin.register(Comment)
|
||||
class CommentAdmin(admin.ModelAdmin):
|
||||
list_filter = ("user",)
|
||||
|
||||
|
||||
@admin.register(Notification)
|
||||
class BaseNotificationAdmin(admin.ModelAdmin):
|
||||
list_filter = ("user_to_notify", "read")
|
||||
|
||||
|
||||
@admin.register(SearchSubscription)
|
||||
class SearchSubscriptionAdmin(admin.ModelAdmin):
|
||||
list_filter = ("owner",)
|
||||
|
||||
|
||||
class ImportantLocationInline(admin.StackedInline):
|
||||
model = ImportantLocation
|
||||
|
||||
|
||||
class IsImportantListFilter(admin.SimpleListFilter):
|
||||
# See https://docs.djangoproject.com/en/5.1/ref/contrib/admin/filters/#modeladmin-list-filters
|
||||
title = _('Is Important Location?')
|
||||
|
||||
parameter_name = 'important'
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
return (
|
||||
('is_important', _('Important Location')),
|
||||
('is_normal', _('Normal Location')),
|
||||
)
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
if self.value() == 'is_important':
|
||||
return queryset.filter(importantlocation__isnull=False)
|
||||
else:
|
||||
return queryset.filter(importantlocation__isnull=True)
|
||||
|
||||
|
||||
@admin.register(Location)
|
||||
class LocationAdmin(admin.ModelAdmin):
|
||||
search_fields = ("name__icontains", "city__icontains")
|
||||
list_filter = [IsImportantListFilter]
|
||||
inlines = [
|
||||
ImportantLocationInline,
|
||||
]
|
||||
|
||||
|
||||
@admin.register(SocialMediaPost)
|
||||
class SocialMediaPostAdmin(admin.ModelAdmin):
|
||||
list_filter = ("platform",)
|
||||
|
||||
|
||||
admin.site.register(Animal)
|
||||
admin.site.register(Species)
|
||||
admin.site.register(Location)
|
||||
admin.site.register(Rule)
|
||||
admin.site.register(Image)
|
||||
admin.site.register(ModerationAction)
|
||||
admin.site.register(Language)
|
||||
admin.site.register(Announcement)
|
||||
admin.site.register(AdoptionNoticeStatus)
|
||||
admin.site.register(Subscriptions)
|
||||
admin.site.register(Log)
|
||||
admin.site.register(Timestamp)
|
||||
|
||||
33
src/fellchensammlung/api/renderers.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from rest_framework.renderers import BaseRenderer
|
||||
import json
|
||||
|
||||
|
||||
class GeoJSONRenderer(BaseRenderer):
|
||||
media_type = 'application/json'
|
||||
format = 'geojson'
|
||||
charset = 'utf-8'
|
||||
|
||||
def render(self, data, accepted_media_type=None, renderer_context=None):
|
||||
features = []
|
||||
for item in data:
|
||||
coords = item["coordinates"]
|
||||
if coords:
|
||||
feature = {
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Point",
|
||||
"coordinates": coords
|
||||
},
|
||||
"properties": {
|
||||
k: v for k, v in item.items()
|
||||
},
|
||||
"id": f"{item['id']}"
|
||||
}
|
||||
features.append(feature)
|
||||
|
||||
geojson = {
|
||||
"type": "FeatureCollection",
|
||||
"generator": "notfellchen",
|
||||
"features": features
|
||||
}
|
||||
return json.dumps(geojson)
|
||||
@@ -1,10 +1,177 @@
|
||||
from ..models import AdoptionNotice
|
||||
from ..models import Animal, RescueOrganization, AdoptionNotice, Species, Image, Location
|
||||
from rest_framework import serializers
|
||||
import math
|
||||
|
||||
|
||||
class ImageSerializer(serializers.ModelSerializer):
|
||||
width = serializers.SerializerMethodField()
|
||||
height = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Image
|
||||
fields = ['id', 'image', 'alt_text', 'width', 'height']
|
||||
|
||||
def get_width(self, obj):
|
||||
return obj.image.width
|
||||
|
||||
def get_height(self, obj):
|
||||
return obj.image.height
|
||||
|
||||
|
||||
class AdoptionNoticeSerializer(serializers.HyperlinkedModelSerializer):
|
||||
location = serializers.PrimaryKeyRelatedField(
|
||||
queryset=Location.objects.all(),
|
||||
required=False,
|
||||
allow_null=True
|
||||
)
|
||||
location_details = serializers.StringRelatedField(source='location', read_only=True)
|
||||
organization = serializers.PrimaryKeyRelatedField(
|
||||
queryset=RescueOrganization.objects.all(),
|
||||
required=False,
|
||||
allow_null=True
|
||||
)
|
||||
organization = serializers.PrimaryKeyRelatedField(
|
||||
queryset=RescueOrganization.objects.all(),
|
||||
required=False,
|
||||
allow_null=True
|
||||
)
|
||||
url = serializers.SerializerMethodField()
|
||||
|
||||
photos = ImageSerializer(many=True, read_only=True)
|
||||
|
||||
def get_url(self, obj):
|
||||
return obj.get_full_url()
|
||||
|
||||
class Meta:
|
||||
model = AdoptionNotice
|
||||
fields = ['created_at', 'last_checked', "searching_since", "name", "description", "further_information", "group_only"]
|
||||
fields = ['created_at', 'last_checked', "searching_since", "name", "description", "further_information",
|
||||
"group_only", "location", "location_details", "organization", "photos", "adoption_notice_status",
|
||||
"url"]
|
||||
|
||||
|
||||
class AdoptionNoticeGeoJSONSerializer(serializers.ModelSerializer):
|
||||
species = serializers.SerializerMethodField()
|
||||
title = serializers.CharField(source='name')
|
||||
url = serializers.SerializerMethodField()
|
||||
location_hr = serializers.SerializerMethodField()
|
||||
coordinates = serializers.SerializerMethodField()
|
||||
image_url = serializers.SerializerMethodField()
|
||||
image_alt = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = AdoptionNotice
|
||||
fields = ('id', 'species', 'title', 'description', 'url', 'location_hr', 'coordinates', 'image_url',
|
||||
'image_alt')
|
||||
|
||||
def get_species(self, obj):
|
||||
return "rat"
|
||||
|
||||
def get_url(self, obj):
|
||||
return obj.get_absolute_url()
|
||||
|
||||
def get_image_url(self, obj):
|
||||
photo = obj.get_photo()
|
||||
if photo is not None:
|
||||
return obj.get_photo().image.url
|
||||
return None
|
||||
|
||||
def get_image_alt(self, obj):
|
||||
photo = obj.get_photo()
|
||||
if photo is not None:
|
||||
return obj.get_photo().alt_text
|
||||
return None
|
||||
|
||||
def get_coordinates(self, obj):
|
||||
"""
|
||||
Coordinates are randomly moved around real location, roughly in a circle. The object id is used as angle so that
|
||||
points are always displayed at the same location (as if they were a seed for a random function).
|
||||
|
||||
It's not exactly a circle, because the earth is round.
|
||||
"""
|
||||
if obj.location:
|
||||
longitude_addition = math.sin(obj.id) / 2000
|
||||
latitude_addition = math.cos(obj.id) / 2000
|
||||
return [obj.location.longitude + longitude_addition, obj.location.latitude + latitude_addition]
|
||||
return None
|
||||
|
||||
def get_location_hr(self, obj):
|
||||
if obj.location:
|
||||
return f"{obj.location}"
|
||||
return None
|
||||
|
||||
|
||||
class RescueOrgeGeoJSONSerializer(serializers.ModelSerializer):
|
||||
name = serializers.CharField()
|
||||
url = serializers.SerializerMethodField()
|
||||
location_hr = serializers.SerializerMethodField()
|
||||
coordinates = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = AdoptionNotice
|
||||
fields = ('id', 'name', 'description', 'url', 'location_hr', 'coordinates')
|
||||
|
||||
def get_url(self, obj):
|
||||
return obj.get_absolute_url()
|
||||
|
||||
def get_coordinates(self, obj):
|
||||
"""
|
||||
Coordinates are randomly moved around real location, roughly in a circle. The object id is used as angle so that
|
||||
points are always displayed at the same location (as if they were a seed for a random function).
|
||||
|
||||
It's not exactly a circle, because the earth is round.
|
||||
"""
|
||||
if obj.location:
|
||||
return [obj.location.longitude, obj.location.latitude]
|
||||
return None
|
||||
|
||||
def get_location_hr(self, obj):
|
||||
if obj.location.city:
|
||||
return f"{obj.location.city}"
|
||||
elif obj.location:
|
||||
return f"{obj.location}"
|
||||
return None
|
||||
|
||||
|
||||
class AnimalCreateSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Animal
|
||||
fields = ["name", "date_of_birth", "description", "species", "sex", "adoption_notice"]
|
||||
|
||||
|
||||
class AnimalGetSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Animal
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class RescueOrganizationSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = RescueOrganization
|
||||
exclude = ["internal_comment", "allows_using_materials"]
|
||||
|
||||
|
||||
class ImageCreateSerializer(serializers.ModelSerializer):
|
||||
@staticmethod
|
||||
def _animal_or_an(value):
|
||||
if not value in ["animal", "adoption_notice"]:
|
||||
raise serializers.ValidationError(
|
||||
'Set either animal or adoption_notice, depending on what type of object the image should be attached to.')
|
||||
|
||||
attach_to_type = serializers.CharField(validators=[_animal_or_an])
|
||||
attach_to = serializers.IntegerField()
|
||||
|
||||
class Meta:
|
||||
model = Image
|
||||
exclude = ["owner"]
|
||||
|
||||
|
||||
class SpeciesSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Species
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class LocationSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Location
|
||||
fields = "__all__"
|
||||
|
||||
@@ -1,8 +1,21 @@
|
||||
from django.urls import path
|
||||
from .views import (
|
||||
AdoptionNoticeApiView
|
||||
AdoptionNoticeApiView,
|
||||
AnimalApiView, RescueOrganizationApiView, AddImageApiView, SpeciesApiView, LocationApiView,
|
||||
AdoptionNoticeGeoJSONView, RescueOrgGeoJSONView, AdoptionNoticePerOrgApiView
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
path('adoption_notice', AdoptionNoticeApiView.as_view()),
|
||||
path("adoption_notice", AdoptionNoticeApiView.as_view(), name="api-adoption-notice-list"),
|
||||
path("adoption_notice.geojson", AdoptionNoticeGeoJSONView.as_view(), name="api-adoption-notice-list-geojson"),
|
||||
path("adoption_notice/<int:id>/", AdoptionNoticeApiView.as_view(), name="api-adoption-notice-detail"),
|
||||
path("animals/", AnimalApiView.as_view(), name="api-animal-list"),
|
||||
path("animals/<int:id>/", AnimalApiView.as_view(), name="api-animal-detail"),
|
||||
path("organizations/", RescueOrganizationApiView.as_view(), name="api-organization-list"),
|
||||
path("organizations.geojson", RescueOrgGeoJSONView.as_view(), name="api-organization-list-geojson"),
|
||||
path("organizations/<int:id>/", RescueOrganizationApiView.as_view(), name="api-organization-detail"),
|
||||
path("organizations/<int:id>/adoption-notices", AdoptionNoticePerOrgApiView.as_view(), name="api-organization-adoption-notices"),
|
||||
path("images/", AddImageApiView.as_view(), name="api-add-image"),
|
||||
path("species/", SpeciesApiView.as_view(), name="api-species-list"),
|
||||
path("locations/", LocationApiView.as_view(), name="api-locations-list"),
|
||||
]
|
||||
|
||||
@@ -1,37 +1,452 @@
|
||||
from django.contrib.auth.models import User
|
||||
from django.db.models import Q
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from rest_framework.generics import ListAPIView
|
||||
|
||||
from fellchensammlung.api.serializers import LocationSerializer, AdoptionNoticeGeoJSONSerializer
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from rest_framework import permissions
|
||||
from ..models import AdoptionNotice
|
||||
from .serializers import AdoptionNoticeSerializer
|
||||
from django.db import transaction
|
||||
from fellchensammlung.models import Log, TrustLevel, Location, AdoptionNoticeStatusChoices
|
||||
from fellchensammlung.tasks import post_adoption_notice_save, post_rescue_org_save
|
||||
from rest_framework import status, serializers
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from .renderers import GeoJSONRenderer
|
||||
from .serializers import (
|
||||
AnimalGetSerializer,
|
||||
AnimalCreateSerializer,
|
||||
RescueOrgeGeoJSONSerializer,
|
||||
AdoptionNoticeSerializer,
|
||||
ImageCreateSerializer,
|
||||
SpeciesSerializer, RescueOrganizationSerializer,
|
||||
)
|
||||
from fellchensammlung.models import Animal, RescueOrganization, AdoptionNotice, Species, Image
|
||||
from drf_spectacular.utils import extend_schema, inline_serializer, OpenApiParameter
|
||||
|
||||
|
||||
class AdoptionNoticeApiView(APIView):
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
{
|
||||
'name': 'id',
|
||||
'required': False,
|
||||
'description': 'ID of the adoption notice to retrieve.',
|
||||
'type': int
|
||||
},
|
||||
],
|
||||
responses={200: AdoptionNoticeSerializer(many=True)}
|
||||
)
|
||||
def get(self, request, *args, **kwargs):
|
||||
serializer_context = {
|
||||
'request': request,
|
||||
}
|
||||
"""
|
||||
Retrieve adoption notices with their related animals and images.
|
||||
"""
|
||||
adoption_notice_id = kwargs.get("id")
|
||||
if adoption_notice_id:
|
||||
try:
|
||||
adoption_notice = AdoptionNotice.objects.get(pk=adoption_notice_id)
|
||||
serializer = AdoptionNoticeSerializer(adoption_notice, context={"request": request})
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
except AdoptionNotice.DoesNotExist:
|
||||
return Response({"error": "Adoption notice not found."}, status=status.HTTP_404_NOT_FOUND)
|
||||
adoption_notices = AdoptionNotice.objects.all()
|
||||
serializer = AdoptionNoticeSerializer(adoption_notices, many=True, context=serializer_context)
|
||||
serializer = AdoptionNoticeSerializer(adoption_notices, many=True, context={"request": request})
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@transaction.atomic
|
||||
@extend_schema(
|
||||
request=AdoptionNoticeSerializer,
|
||||
responses={201: 'Adoption notice created successfully!'}
|
||||
)
|
||||
def post(self, request, *args, **kwargs):
|
||||
data = {
|
||||
'name': request.data.get('name'),
|
||||
"searching_since": request.data.get('searching_since'),
|
||||
"description": request.data.get('description'),
|
||||
"organization": request.data.get('organization'),
|
||||
"further_information": request.data.get('further_information'),
|
||||
"location_string": request.data.get('location_string'),
|
||||
"group_only": request.data.get('group_only'),
|
||||
"owner": request.data.get('owner')
|
||||
}
|
||||
serializer = AdoptionNoticeSerializer(data=data)
|
||||
"""
|
||||
API view to add an adoption notice.
|
||||
"""
|
||||
serializer = AdoptionNoticeSerializer(data=request.data, context={'request': request})
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
adoption_notice = serializer.save(owner=request.user_to_notify)
|
||||
|
||||
# Add the location
|
||||
post_adoption_notice_save.delay_on_commit(adoption_notice.pk)
|
||||
|
||||
# Only set active when user has trust level moderator or higher
|
||||
if request.user_to_notify.trust_level >= TrustLevel.MODERATOR:
|
||||
adoption_notice.adoption_notice_status = AdoptionNoticeStatusChoices.Active.SEARCHING
|
||||
else:
|
||||
adoption_notice.adoption_notice_status = AdoptionNoticeStatusChoices.AwaitingAction.WAITING_FOR_REVIEW
|
||||
|
||||
# Log the action
|
||||
Log.objects.create(
|
||||
user=request.user_to_notify,
|
||||
action="add_adoption_notice",
|
||||
text=f"{request.user_to_notify} added adoption notice {adoption_notice.pk} via API",
|
||||
)
|
||||
|
||||
# Return success response with new adoption notice details
|
||||
return Response(
|
||||
{"message": "Adoption notice created successfully!", "id": adoption_notice.pk},
|
||||
status=status.HTTP_201_CREATED,
|
||||
)
|
||||
|
||||
|
||||
class AnimalApiView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@extend_schema(
|
||||
responses=AnimalGetSerializer
|
||||
)
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""
|
||||
Get list of animals or a specific animal by ID.
|
||||
"""
|
||||
animal_id = kwargs.get("id")
|
||||
if animal_id:
|
||||
try:
|
||||
animal = Animal.objects.get(pk=animal_id)
|
||||
serializer = AnimalGetSerializer(animal, context={"request": request})
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
except Animal.DoesNotExist:
|
||||
return Response({"error": "Animal not found."}, status=status.HTTP_404_NOT_FOUND)
|
||||
animals = Animal.objects.all()
|
||||
serializer = AnimalGetSerializer(animals, many=True, context={"request": request})
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@transaction.atomic
|
||||
@extend_schema(
|
||||
request=AnimalCreateSerializer,
|
||||
responses={201: inline_serializer(
|
||||
name='Animal',
|
||||
fields={
|
||||
'id': serializers.IntegerField(),
|
||||
"message": serializers.Field()}),
|
||||
400: "json"}
|
||||
)
|
||||
@transaction.atomic
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""
|
||||
Create a new animal.
|
||||
"""
|
||||
serializer = AnimalCreateSerializer(data=request.data, context={"request": request})
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
animal = serializer.save(owner=request.user_to_notify)
|
||||
return Response(
|
||||
{"message": "Animal created successfully!", "id": animal.id},
|
||||
status=status.HTTP_201_CREATED,
|
||||
)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class RescueOrganizationApiView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
{
|
||||
'name': 'id',
|
||||
'required': False,
|
||||
'description': 'ID of the rescue organization to retrieve.',
|
||||
'type': int
|
||||
},
|
||||
{
|
||||
'name': 'trusted',
|
||||
'required': False,
|
||||
'description': 'Filter by trusted status (true/false).',
|
||||
'type': bool
|
||||
},
|
||||
{
|
||||
'name': 'external_object_identifier',
|
||||
'required': False,
|
||||
'description': 'Filter by external object identifier. Use "None" to filter for an empty field',
|
||||
'type': str
|
||||
},
|
||||
{
|
||||
'name': 'external_source_identifier',
|
||||
'required': False,
|
||||
'description': 'Filter by external source identifier. Use "None" to filter for an empty field',
|
||||
'type': str
|
||||
},
|
||||
{
|
||||
'name': 'search',
|
||||
'required': False,
|
||||
'description': 'Search by organization name or location name/city.',
|
||||
'type': str
|
||||
},
|
||||
],
|
||||
responses={200: RescueOrganizationSerializer(many=True)}
|
||||
)
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""
|
||||
Get list of rescue organizations or a specific organization by ID or get a list with available filters for
|
||||
- external_object_identifier
|
||||
- external_source_identifier
|
||||
"""
|
||||
org_id = request.query_params.get("id")
|
||||
external_object_identifier = request.query_params.get("external_object_identifier")
|
||||
external_source_identifier = request.query_params.get("external_source_identifier")
|
||||
search_query = request.query_params.get("search")
|
||||
|
||||
if org_id:
|
||||
try:
|
||||
organization = RescueOrganization.objects.get(pk=org_id)
|
||||
serializer = RescueOrganizationSerializer(organization, context={"request": request})
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
except RescueOrganization.DoesNotExist:
|
||||
return Response({"error": "Organization not found."}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
organizations = RescueOrganization.objects.all()
|
||||
|
||||
if external_object_identifier:
|
||||
if external_object_identifier == "None":
|
||||
external_object_identifier = None
|
||||
organizations = organizations.filter(external_object_identifier=external_object_identifier)
|
||||
|
||||
if external_source_identifier:
|
||||
if external_source_identifier == "None":
|
||||
external_source_identifier = None
|
||||
organizations = organizations.filter(external_source_identifier=external_source_identifier)
|
||||
if search_query:
|
||||
organizations = organizations.filter(
|
||||
Q(name__icontains=search_query) |
|
||||
Q(location_string__icontains=search_query) |
|
||||
Q(location__name__icontains=search_query) |
|
||||
Q(location__city__icontains=search_query)
|
||||
)
|
||||
if organizations.count() == 0:
|
||||
return Response({"error": "No organizations found."}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
serializer = RescueOrganizationSerializer(organizations, many=True, context={"request": request})
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@transaction.atomic
|
||||
@extend_schema(
|
||||
request=RescueOrganizationSerializer,
|
||||
responses={201: 'Rescue organization created successfully!'}
|
||||
)
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""
|
||||
Create or update a rescue organization.
|
||||
"""
|
||||
serializer = RescueOrganizationSerializer(data=request.data, context={"request": request})
|
||||
if serializer.is_valid():
|
||||
rescue_org = serializer.save()
|
||||
if rescue_org.location is None:
|
||||
# Add the location
|
||||
post_rescue_org_save.delay_on_commit(rescue_org.pk)
|
||||
return Response(
|
||||
{"message": "Rescue organization created successfully!", "id": rescue_org.id},
|
||||
status=status.HTTP_201_CREATED,
|
||||
)
|
||||
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@transaction.atomic
|
||||
@extend_schema(
|
||||
request=RescueOrganizationSerializer,
|
||||
responses={200: 'Rescue organization updated successfully!'}
|
||||
)
|
||||
def patch(self, request, *args, **kwargs):
|
||||
"""
|
||||
Partially update a rescue organization.
|
||||
"""
|
||||
org_id = request.data.get("id")
|
||||
if not org_id:
|
||||
return Response({"error": "ID is required for updating."}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
organization = RescueOrganization.objects.get(pk=org_id)
|
||||
except RescueOrganization.DoesNotExist:
|
||||
return Response({"error": "Organization not found."}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
serializer = RescueOrganizationSerializer(organization, data=request.data, partial=True)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response({"message": "Rescue organization updated successfully!"}, status=status.HTTP_200_OK)
|
||||
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class AddImageApiView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@transaction.atomic
|
||||
@extend_schema(
|
||||
request=ImageCreateSerializer,
|
||||
responses={201: 'Image added successfully!'}
|
||||
)
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""
|
||||
Add an image to an animal or adoption notice.
|
||||
"""
|
||||
serializer = ImageCreateSerializer(data=request.data, context={"request": request})
|
||||
if serializer.is_valid():
|
||||
if serializer.validated_data["attach_to_type"] == "animal":
|
||||
object_to_attach_to = Animal.objects.get(id=serializer.validated_data["attach_to"])
|
||||
elif serializer.validated_data["attach_to_type"] == "adoption_notice":
|
||||
object_to_attach_to = AdoptionNotice.objects.get(id=serializer.validated_data["attach_to"])
|
||||
else:
|
||||
raise ValueError("Unknown attach_to_type given, should not happen. Check serializer")
|
||||
serializer.validated_data.pop('attach_to_type', None)
|
||||
serializer.validated_data.pop('attach_to', None)
|
||||
image = serializer.save(owner=request.user_to_notify)
|
||||
object_to_attach_to.photos.add(image)
|
||||
return Response(
|
||||
{"message": "Image added successfully!", "id": image.id},
|
||||
status=status.HTTP_201_CREATED,
|
||||
)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class SpeciesApiView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@extend_schema(
|
||||
responses={200: SpeciesSerializer(many=True)}
|
||||
)
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""
|
||||
Retrieve a list of species.
|
||||
"""
|
||||
species = Species.objects.all()
|
||||
serializer = SpeciesSerializer(species, many=True, context={"request": request})
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class LocationApiView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
{
|
||||
'name': 'id',
|
||||
'required': False,
|
||||
'description': 'ID of the location to retrieve.',
|
||||
'type': int
|
||||
},
|
||||
],
|
||||
responses={200: LocationSerializer(many=True)}
|
||||
)
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""
|
||||
Retrieve a location
|
||||
"""
|
||||
location_id = kwargs.get("id")
|
||||
if location_id:
|
||||
try:
|
||||
location = Location.objects.get(pk=location_id)
|
||||
serializer = LocationSerializer(location, context={"request": request})
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
except Location.DoesNotExist:
|
||||
return Response({"error": "Location not found."}, status=status.HTTP_404_NOT_FOUND)
|
||||
locations = Location.objects.all()
|
||||
serializer = LocationSerializer(locations, many=True, context={"request": request})
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@transaction.atomic
|
||||
@extend_schema(
|
||||
request=LocationSerializer,
|
||||
responses={201: 'Location created successfully!'}
|
||||
)
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""
|
||||
API view to add a location
|
||||
"""
|
||||
serializer = LocationSerializer(data=request.data, context={'request': request})
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
location = serializer.save()
|
||||
|
||||
# Log the action
|
||||
Log.objects.create(
|
||||
user=request.user,
|
||||
action="add_location",
|
||||
text=f"{request.user} added adoption notice {location.pk} via API",
|
||||
)
|
||||
|
||||
# Return success response with new adoption notice details
|
||||
return Response(
|
||||
{"message": "Location created successfully!", "id": location.pk},
|
||||
status=status.HTTP_201_CREATED,
|
||||
)
|
||||
|
||||
|
||||
class AdoptionNoticeGeoJSONView(ListAPIView):
|
||||
queryset = AdoptionNotice.objects.select_related('location').filter(location__isnull=False).filter(
|
||||
adoption_notice_status__in=AdoptionNoticeStatusChoices.Active.values)
|
||||
serializer_class = AdoptionNoticeGeoJSONSerializer
|
||||
renderer_classes = [GeoJSONRenderer]
|
||||
|
||||
|
||||
class RescueOrgGeoJSONView(ListAPIView):
|
||||
queryset = RescueOrganization.objects.select_related('location').filter(location__isnull=False)
|
||||
serializer_class = RescueOrgeGeoJSONSerializer
|
||||
renderer_classes = [GeoJSONRenderer]
|
||||
|
||||
|
||||
class AdoptionNoticePerOrgApiView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name='id',
|
||||
required=False,
|
||||
description='ID of the rescue organization from which to retrieve adoption notices.',
|
||||
type=OpenApiTypes.INT
|
||||
),
|
||||
OpenApiParameter(
|
||||
name='in_hierarchy',
|
||||
type=OpenApiTypes.BOOL,
|
||||
required=False,
|
||||
description='Show all Adoption Notices in hierarchy.',
|
||||
),
|
||||
OpenApiParameter(
|
||||
name='status',
|
||||
type=OpenApiTypes.STR,
|
||||
required=False,
|
||||
description='Show all Adoption Notices in a certain status. Comma separated list of values e.g. '
|
||||
'"active,closed"',
|
||||
),
|
||||
],
|
||||
responses={200: AdoptionNoticeSerializer(many=True)}
|
||||
)
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""
|
||||
Retrieve adoption notices with their related animals and images.
|
||||
"""
|
||||
org_id = kwargs.get("id")
|
||||
in_hierarchy = request.query_params.get("in_hierarchy")
|
||||
an_status = request.query_params.get("status")
|
||||
try:
|
||||
org = RescueOrganization.objects.get(id=org_id)
|
||||
except RescueOrganization.DoesNotExist:
|
||||
return Response({"error": "Rescue Organization notice not found."}, status=status.HTTP_404_NOT_FOUND)
|
||||
if in_hierarchy:
|
||||
adoption_notices = org.adoption_notices_in_hierarchy
|
||||
else:
|
||||
adoption_notices = AdoptionNotice.objects.filter(organization=org)
|
||||
if an_status:
|
||||
status_list = an_status.lower().strip().split(",")
|
||||
temporary_an_storage = []
|
||||
if "active" in status_list:
|
||||
active_ans = [adoption_notice for adoption_notice in adoption_notices if
|
||||
adoption_notice.adoption_notice_status in AdoptionNoticeStatusChoices.Active.values]
|
||||
temporary_an_storage.extend(active_ans)
|
||||
if "closed" in status_list:
|
||||
closed_ans = [adoption_notice for adoption_notice in adoption_notices if
|
||||
adoption_notice.adoption_notice_status in AdoptionNoticeStatusChoices.Closed.values]
|
||||
temporary_an_storage.extend(closed_ans)
|
||||
if "disabled" in status_list:
|
||||
disabled_ans = [adoption_notice for adoption_notice in adoption_notices if
|
||||
adoption_notice.adoption_notice_status in AdoptionNoticeStatusChoices.Disabled.values]
|
||||
temporary_an_storage.extend(disabled_ans)
|
||||
if "awaiting_action" in status_list:
|
||||
awaiting_action_ans = [adoption_notice for adoption_notice in adoption_notices if
|
||||
adoption_notice.adoption_notice_status in AdoptionNoticeStatusChoices.AwaitingAction.values]
|
||||
temporary_an_storage.extend(awaiting_action_ans)
|
||||
adoption_notices = temporary_an_storage
|
||||
serializer = AdoptionNoticeSerializer(adoption_notices, many=True, context={"request": request})
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@@ -15,3 +15,4 @@ class FellchensammlungConfig(AppConfig):
|
||||
except Permission.DoesNotExist:
|
||||
pass
|
||||
post_migrate.connect(ensure_languages, sender=self)
|
||||
import fellchensammlung.receivers
|
||||
|
||||
0
src/fellchensammlung/aviews/__init__.py
Normal file
37
src/fellchensammlung/aviews/embeddables.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.views.decorators.clickjacking import xframe_options_exempt
|
||||
|
||||
from fellchensammlung.aviews.helpers import headers
|
||||
from fellchensammlung.models import RescueOrganization, AdoptionNotice, Species
|
||||
|
||||
|
||||
@xframe_options_exempt
|
||||
@headers({"X-Robots-Tag": "noindex"})
|
||||
def list_ans_per_rescue_organization(request, rescue_organization_id, species_slug=None, active=True):
|
||||
expand = request.GET.get("expand")
|
||||
background_color = request.GET.get("background_color")
|
||||
if expand is not None:
|
||||
expand = True
|
||||
else:
|
||||
expand = False
|
||||
org = get_object_or_404(RescueOrganization, pk=rescue_organization_id)
|
||||
|
||||
# Get only active adoption notices or all
|
||||
if active:
|
||||
adoption_notices_of_org = org.adoption_notices_in_hierarchy_divided_by_status[0]
|
||||
else:
|
||||
adoption_notices_of_org = org.adoption_notices
|
||||
|
||||
# Filter for Species if necessary
|
||||
if species_slug is None:
|
||||
adoption_notices = adoption_notices_of_org
|
||||
else:
|
||||
species = get_object_or_404(Species, slug=species_slug)
|
||||
adoption_notices = [adoption_notice for adoption_notice in adoption_notices_of_org if
|
||||
species in adoption_notice.species]
|
||||
|
||||
template = 'fellchensammlung/embeddables/list-adoption-notices.html'
|
||||
return render(request, template,
|
||||
context={"adoption_notices": adoption_notices,
|
||||
"expand": expand,
|
||||
"background_color": background_color})
|
||||
23
src/fellchensammlung/aviews/helpers.py
Normal file
@@ -0,0 +1,23 @@
|
||||
|
||||
def headers(headers):
|
||||
"""Decorator adding arbitrary HTTP headers to the response.
|
||||
|
||||
This decorator adds HTTP headers specified in the argument (map), to the
|
||||
HTTPResponse returned by the function being decorated.
|
||||
|
||||
Example:
|
||||
|
||||
@headers({'Refresh': '10', 'X-Bender': 'Bite my shiny, metal ass!'})
|
||||
def index(request):
|
||||
....
|
||||
Source: https://djangosnippets.org/snippets/275/
|
||||
"""
|
||||
def headers_wrapper(fun):
|
||||
def wrapped_function(*args, **kwargs):
|
||||
response = fun(*args, **kwargs)
|
||||
for key in headers:
|
||||
response[key] = headers[key]
|
||||
return response
|
||||
return wrapped_function
|
||||
return headers_wrapper
|
||||
|
||||
12
src/fellchensammlung/aviews/urls.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from django.urls import path
|
||||
|
||||
from . import embeddables
|
||||
|
||||
urlpatterns = [
|
||||
path("tierschutzorganisationen/<int:rescue_organization_id>/vermittlungen/",
|
||||
embeddables.list_ans_per_rescue_organization,
|
||||
name="list-adoption-notices-for-rescue-organization"),
|
||||
path("tierschutzorganisationen/<int:rescue_organization_id>/vermittlungen/<slug:species_slug>/",
|
||||
embeddables.list_ans_per_rescue_organization,
|
||||
name="list-adoption-notices-for-rescue-organization-species"),
|
||||
]
|
||||
@@ -1,12 +1,21 @@
|
||||
from django import forms
|
||||
|
||||
from .models import AdoptionNotice, Animal, Image, ReportAdoptionNotice, ReportComment, ModerationAction, User, Species, \
|
||||
Comment
|
||||
Comment, SexChoicesWithAll, DistanceChoices, SpeciesSpecificURL, RescueOrganization
|
||||
from django_registration.forms import RegistrationForm
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Submit, Layout, Fieldset, HTML, Row, Column, Field, Hidden
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from notfellchen.settings import MEDIA_URL
|
||||
from crispy_forms.layout import Div
|
||||
|
||||
|
||||
def animal_validator(value: str):
|
||||
value = value.lower()
|
||||
animal_list = ["ratte", "farbratte", "katze", "hund", "kaninchen", "hase", "kuh", "fuchs", "cow", "rat", "cat",
|
||||
"dog", "rabbit", "fox", "fancy rat"]
|
||||
if value not in animal_list:
|
||||
raise forms.ValidationError(_("Dieses Tier kenne ich nicht. Probier ein anderes"))
|
||||
|
||||
|
||||
class DateInput(forms.DateInput):
|
||||
@@ -14,92 +23,46 @@ class DateInput(forms.DateInput):
|
||||
|
||||
|
||||
class AdoptionNoticeForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
if 'in_adoption_notice_creation_flow' in kwargs:
|
||||
in_flow = kwargs.pop('in_adoption_notice_creation_flow')
|
||||
else:
|
||||
in_flow = False
|
||||
super().__init__(*args, **kwargs)
|
||||
self.helper = FormHelper()
|
||||
|
||||
self.helper.form_id = 'form-adoption-notice'
|
||||
self.helper.form_class = 'card'
|
||||
self.helper.form_method = 'post'
|
||||
|
||||
if in_flow:
|
||||
submit = Submit('save-and-add-another-animal', _('Speichern'))
|
||||
|
||||
else:
|
||||
submit = Submit('submit', _('Speichern'))
|
||||
|
||||
self.helper.layout = Layout(
|
||||
Fieldset(
|
||||
_('Vermittlungsdetails'),
|
||||
'name',
|
||||
'species',
|
||||
'num_animals',
|
||||
'date_of_birth',
|
||||
'sex',
|
||||
'group_only',
|
||||
'searching_since',
|
||||
'location_string',
|
||||
'description',
|
||||
'further_information',
|
||||
),
|
||||
submit)
|
||||
template_name = "fellchensammlung/forms/form_snippets.html"
|
||||
|
||||
class Meta:
|
||||
model = AdoptionNotice
|
||||
fields = ['name', "group_only", "further_information", "description", "searching_since", "location_string"]
|
||||
|
||||
|
||||
class AdoptionNoticeFormWithDateWidget(AdoptionNoticeForm):
|
||||
class Meta:
|
||||
model = AdoptionNotice
|
||||
fields = ['name', "group_only", "further_information", "description", "searching_since", "location_string"]
|
||||
fields = ['name', "group_only", "further_information", "description", "searching_since", "location_string",
|
||||
"organization"]
|
||||
widgets = {
|
||||
'searching_since': DateInput(),
|
||||
'searching_since': DateInput(format=('%Y-%m-%d')),
|
||||
}
|
||||
|
||||
|
||||
|
||||
class AnimalForm(forms.ModelForm):
|
||||
class AdoptionNoticeFormAutoAnimal(AdoptionNoticeForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
if 'in_adoption_notice_creation_flow' in kwargs:
|
||||
adding = kwargs.pop('in_adoption_notice_creation_flow')
|
||||
else:
|
||||
adding = False
|
||||
super().__init__(*args, **kwargs)
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_class = 'form-animal card'
|
||||
if adding:
|
||||
self.helper.add_input(Submit('save-and-add-another-animal', _('Speichern und weiteres Tier hinzufügen')))
|
||||
self.helper.add_input(Submit('save-and-finish', _('Speichern und beenden')))
|
||||
else:
|
||||
self.helper.add_input(Submit('submit', _('Speichern'), css_class="btn"))
|
||||
|
||||
class Meta:
|
||||
model = Animal
|
||||
fields = ["name", "date_of_birth", "species", "sex", "description"]
|
||||
|
||||
|
||||
class AnimalFormWithDateWidget(AnimalForm):
|
||||
class Meta:
|
||||
model = Animal
|
||||
fields = ["name", "date_of_birth", "species", "sex", "description"]
|
||||
widgets = {
|
||||
'date_of_birth': DateInput(),
|
||||
}
|
||||
|
||||
class AdoptionNoticeFormWithDateWidgetAutoAnimal(AdoptionNoticeFormWithDateWidget):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AdoptionNoticeFormWithDateWidgetAutoAnimal, self).__init__(*args, **kwargs)
|
||||
super(AdoptionNoticeFormAutoAnimal, self).__init__(*args, **kwargs)
|
||||
self.fields["num_animals"] = forms.fields.IntegerField(min_value=1, max_value=30, label=_("Zahl der Tiere"))
|
||||
animal_form = AnimalForm()
|
||||
self.fields["species"] = animal_form.fields["species"]
|
||||
self.fields["sex"] = animal_form.fields["sex"]
|
||||
self.fields["date_of_birth"] = animal_form.fields["date_of_birth"]
|
||||
self.fields["date_of_birth"].widget = DateInput()
|
||||
self.fields["date_of_birth"].widget = DateInput(format=('%Y-%m-%d'))
|
||||
|
||||
|
||||
class AnimalForm(forms.ModelForm):
|
||||
template_name = "fellchensammlung/forms/form_snippets.html"
|
||||
|
||||
class Meta:
|
||||
model = Animal
|
||||
fields = ["name", "date_of_birth", "species", "sex", "description"]
|
||||
|
||||
widgets = {
|
||||
'date_of_birth': DateInput(format=('%Y-%m-%d'))
|
||||
}
|
||||
|
||||
|
||||
class UpdateRescueOrgRegularCheckStatus(forms.ModelForm):
|
||||
template_name = "fellchensammlung/forms/form_snippets.html"
|
||||
|
||||
class Meta:
|
||||
model = RescueOrganization
|
||||
fields = ["regular_check_status"]
|
||||
|
||||
|
||||
class ImageForm(forms.ModelForm):
|
||||
@@ -113,11 +76,21 @@ class ImageForm(forms.ModelForm):
|
||||
self.helper.form_id = 'form-animal-photo'
|
||||
self.helper.form_class = 'card'
|
||||
self.helper.form_method = 'post'
|
||||
|
||||
if in_flow:
|
||||
self.helper.add_input(Submit('save-and-add-another', _('Speichern und weiteres Foto hinzufügen')))
|
||||
self.helper.add_input(Submit('submit', _('Speichern')))
|
||||
submits = Div(Submit('submit', _('Speichern')),
|
||||
Submit('save-and-add-another', _('Speichern und weiteres Foto hinzufügen')),
|
||||
css_class="container-edit-buttons")
|
||||
else:
|
||||
self.helper.add_input(Submit('submit', _('Submit')))
|
||||
submits = Fieldset(Submit('submit', _('Speichern')), css_class="container-edit-buttons")
|
||||
self.helper.layout = Layout(
|
||||
Div(
|
||||
'image',
|
||||
'alt_text',
|
||||
css_class="spaced",
|
||||
),
|
||||
submits
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Image
|
||||
@@ -125,53 +98,76 @@ class ImageForm(forms.ModelForm):
|
||||
|
||||
|
||||
class ReportAdoptionNoticeForm(forms.ModelForm):
|
||||
template_name = "fellchensammlung/forms/form_snippets.html"
|
||||
|
||||
class Meta:
|
||||
model = ReportAdoptionNotice
|
||||
fields = ('reported_broken_rules', 'user_comment')
|
||||
|
||||
|
||||
class ReportCommentForm(forms.ModelForm):
|
||||
template_name = "fellchensammlung/forms/form_snippets.html"
|
||||
|
||||
class Meta:
|
||||
model = ReportComment
|
||||
fields = ('reported_broken_rules', 'user_comment')
|
||||
|
||||
|
||||
class CommentForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_class = 'form-comments'
|
||||
self.helper.add_input(Hidden('action', 'comment'))
|
||||
self.helper.add_input(Submit('submit', _('Kommentieren'), css_class="btn2"))
|
||||
|
||||
class Meta:
|
||||
model = Comment
|
||||
fields = ('text',)
|
||||
|
||||
|
||||
class SpeciesURLForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = SpeciesSpecificURL
|
||||
fields = ('species', 'url')
|
||||
|
||||
|
||||
class RescueOrgInternalComment(forms.ModelForm):
|
||||
class Meta:
|
||||
model = RescueOrganization
|
||||
fields = ('internal_comment',)
|
||||
|
||||
|
||||
class ModerationActionForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = ModerationAction
|
||||
fields = ('action', 'public_comment', 'private_comment')
|
||||
|
||||
|
||||
class AddedRegistrationForm(forms.Form):
|
||||
captcha = forms.CharField(validators=[animal_validator], label=_("Nenne eine bekannte Tierart"), help_text=_(
|
||||
"Bitte nenne hier eine bekannte Tierart (z.B. ein Tier das an der Leine geführt wird). Das Fragen wir dich um sicherzustellen, dass du kein Roboter bist."))
|
||||
|
||||
def signup(self, request, user):
|
||||
pass
|
||||
|
||||
|
||||
class CustomRegistrationForm(RegistrationForm):
|
||||
class Meta(RegistrationForm.Meta):
|
||||
model = User
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_id = 'form-registration'
|
||||
self.helper.form_class = 'card'
|
||||
template_name = "fellchensammlung/forms/form_snippets.html"
|
||||
|
||||
self.helper.add_input(Submit('submit', _('Registrieren'), css_class="btn"))
|
||||
|
||||
|
||||
def _get_distances():
|
||||
return {i: i for i in [10, 20, 50, 100, 200, 500]}
|
||||
captcha = forms.CharField(validators=[animal_validator], label=_("Nenne eine bekannte Tierart"), help_text=_(
|
||||
"Bitte nenne hier eine bekannte Tierart (z.B. ein Tier das an der Leine geführt wird). Das Fragen wir dich um sicherzustellen, dass du kein Roboter bist."))
|
||||
|
||||
|
||||
class AdoptionNoticeSearchForm(forms.Form):
|
||||
location = forms.CharField(max_length=20, label=_("Stadt"))
|
||||
max_distance = forms.ChoiceField(choices=_get_distances, label=_("Max. Distanz"))
|
||||
template_name = "fellchensammlung/forms/form_snippets.html"
|
||||
|
||||
sex = forms.ChoiceField(choices=SexChoicesWithAll, label=_("Geschlecht"), required=False,
|
||||
initial=SexChoicesWithAll.ALL)
|
||||
max_distance = forms.ChoiceField(choices=DistanceChoices, initial=DistanceChoices.ONE_HUNDRED,
|
||||
label=_("Suchradius"))
|
||||
location_string = forms.CharField(max_length=100, label=_("Stadt"), required=False)
|
||||
|
||||
|
||||
class RescueOrgSearchForm(forms.Form):
|
||||
template_name = "fellchensammlung/forms/form_snippets.html"
|
||||
|
||||
location_string = forms.CharField(max_length=100, label=_("Stadt"), required=False)
|
||||
max_distance = forms.ChoiceField(choices=DistanceChoices, initial=DistanceChoices.TWENTY,
|
||||
label=_("Suchradius"))
|
||||
|
||||
@@ -1,33 +1,43 @@
|
||||
import django.conf.global_settings
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.html import strip_tags
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import gettext
|
||||
from django.conf import settings
|
||||
from django.core import mail
|
||||
from django.db.models import Q, Min
|
||||
from fellchensammlung.models import User
|
||||
from notfellchen.settings import host
|
||||
from fellchensammlung.models import User, Notification, TrustLevel, NotificationTypeChoices
|
||||
from fellchensammlung.tools.model_helpers import ndm
|
||||
|
||||
NEWLINE = "\r\n"
|
||||
|
||||
def mail_admins_new_report(report):
|
||||
subject = _("Neue Meldung")
|
||||
for moderator in User.objects.filter(trust_level__gt=User.TRUST_LEVEL[User.MODERATOR]):
|
||||
greeting = _("Moin,") + "{NEWLINE}"
|
||||
new_report_text = _("es wurde ein Regelverstoß gemeldet.") + "{NEWLINE}"
|
||||
if len(report.reported_broken_rules.all()) > 0:
|
||||
reported_rules_text = (f"Ein Verstoß gegen die folgenden Regeln wurde gemeldet:{NEWLINE}"
|
||||
f"- {f'{NEWLINE} - '.join([str(r) for r in report.reported_broken_rules.all()])}{NEWLINE}")
|
||||
def notify_mods_new_report(report, notification_type):
|
||||
"""
|
||||
Sends an e-mail to all users that should handle the report.
|
||||
"""
|
||||
for moderator in User.objects.filter(trust_level__gt=TrustLevel.MODERATOR):
|
||||
if notification_type == NotificationTypeChoices.NEW_REPORT_AN:
|
||||
title = _("Vermittlung gemeldet")
|
||||
elif notification_type == NotificationTypeChoices.NEW_REPORT_COMMENT:
|
||||
title = _("Kommentar gemeldet")
|
||||
else:
|
||||
reported_rules_text = f"Es wurden keine Regeln angegeben gegen die Verstoßen wurde.{NEWLINE}"
|
||||
if report.user_comment:
|
||||
comment_text = f'Kommentar zum Report: "{report.user_comment}"{NEWLINE}'
|
||||
else:
|
||||
comment_text = f"Es wurde kein Kommentar hinzugefügt.{NEWLINE}"
|
||||
raise NotImplementedError
|
||||
notification = Notification.objects.create(
|
||||
notification_type=notification_type,
|
||||
user_to_notify=moderator,
|
||||
report=report,
|
||||
title=title,
|
||||
)
|
||||
notification.save()
|
||||
|
||||
report_url = "https://" + host + report.get_absolute_url()
|
||||
link_text = f"Um alle Details zu sehen, geh bitte auf: {report_url}"
|
||||
body_text = greeting + new_report_text + reported_rules_text + comment_text + link_text
|
||||
message = mail.EmailMessage(subject, body_text, settings.DEFAULT_FROM_EMAIL, [moderator.email])
|
||||
print("Sending email to ", moderator.email)
|
||||
message.send()
|
||||
|
||||
def send_notification_email(notification_pk):
|
||||
notification = Notification.objects.get(pk=notification_pk)
|
||||
|
||||
subject = f"{notification.title}"
|
||||
context = {"notification": notification, }
|
||||
html_message = render_to_string(ndm[notification.notification_type].email_html_template, context)
|
||||
plain_message = render_to_string(ndm[notification.notification_type].email_plain_template, context)
|
||||
|
||||
mail.send_mail(subject, plain_message, settings.DEFAULT_FROM_EMAIL,
|
||||
[notification.user_to_notify.email],
|
||||
html_message=html_message)
|
||||
|
||||
10
src/fellchensammlung/management/commands/dedup_locations.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from django.core.management import BaseCommand
|
||||
from fellchensammlung.tools.admin import dedup_locations
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Deduplicate locations based on place_id'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
dedup_locations()
|
||||
|
||||
11
src/fellchensammlung/management/commands/export_contacts.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from django.core.management import BaseCommand
|
||||
|
||||
from fellchensammlung.tools.admin import export_orgs_as_vcf
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Export organizations with phone number as contacts in vcf format'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
export_orgs_as_vcf()
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
from django.core.management import BaseCommand
|
||||
from fellchensammlung.tools.admin import mask_organization_contact_data
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Mask e-mail addresses and phone numbers of organizations for testing purposes.'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("domain", type=str)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
domain = options["domain"]
|
||||
mask_organization_contact_data(domain)
|
||||
@@ -7,7 +7,7 @@ from fellchensammlung import baker_recipes
|
||||
from model_bakery import baker
|
||||
|
||||
from fellchensammlung.models import AdoptionNotice, Species, Animal, Image, ModerationAction, User, Rule, \
|
||||
Report, Comment, ReportAdoptionNotice
|
||||
Report, Comment, ReportAdoptionNotice, TrustLevel
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
@@ -101,10 +101,10 @@ class Command(BaseCommand):
|
||||
|
||||
User.objects.create_user('test', password='foobar')
|
||||
admin1 = User.objects.create_superuser(username="admin", password="admin", email="admin1@example.org",
|
||||
trust_level=User.TRUST_LEVEL[User.ADMIN])
|
||||
trust_level=TrustLevel.ADMIN)
|
||||
|
||||
mod1 = User.objects.create_user(username="mod1", password="mod", email="mod1@example.org",
|
||||
trust_level=User.TRUST_LEVEL[User.MODERATOR])
|
||||
trust_level=TrustLevel.MODERATOR)
|
||||
|
||||
comment1 = baker.make(Comment, user=admin1, text="This is a comment", adoption_notice=adoption1)
|
||||
comment2 = baker.make(Comment,
|
||||
|
||||
19
src/fellchensammlung/management/commands/sync_to_twenty.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from django.core.management import BaseCommand
|
||||
from tqdm import tqdm
|
||||
|
||||
from fellchensammlung.models import RescueOrganization
|
||||
from fellchensammlung.tools.twenty import sync_rescue_org_to_twenty
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Send rescue organizations as companies to twenty'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("base_url", type=str)
|
||||
parser.add_argument("token", type=str)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
base_url = options["base_url"]
|
||||
token = options["token"]
|
||||
for rescue_org in tqdm(RescueOrganization.objects.all()):
|
||||
sync_rescue_org_to_twenty(rescue_org, base_url, token)
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.1 on 2024-11-13 15:33
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0015_rescueorganization_comment'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='rescueorganization',
|
||||
name='phone_number',
|
||||
field=models.CharField(blank=True, max_length=15, null=True, verbose_name='Telefonnummer'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.1.1 on 2024-11-14 06:42
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0016_rescueorganization_phone_number'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='organization_affiliation',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='fellchensammlung.rescueorganization', verbose_name='Organisation'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.1 on 2024-11-14 17:58
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0017_user_organization_affiliation'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='rescueorganization',
|
||||
name='description',
|
||||
field=models.TextField(blank=True, null=True, verbose_name='Beschreibung'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.1 on 2024-11-14 18:30
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0018_rescueorganization_description'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='rescueorganization',
|
||||
old_name='comment',
|
||||
new_name='internal_comment',
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.1 on 2024-11-14 18:31
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0019_rename_comment_rescueorganization_internal_comment'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='rescueorganization',
|
||||
name='internal_comment',
|
||||
field=models.TextField(blank=True, null=True, verbose_name='Interner Kommentar'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.1.1 on 2024-11-14 20:15
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0020_alter_rescueorganization_internal_comment'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='reason_for_signup',
|
||||
field=models.TextField(default='-', verbose_name='Grund für die Registrierung'),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.1.1 on 2024-11-20 18:50
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0021_user_reason_for_signup'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='reason_for_signup',
|
||||
field=models.TextField(help_text="Wir würden gerne wissen warum du dich registriertst, ob du dich z.B. Tiere eines bestimmten Tierheim einstellen willst 'nur mal gucken' willst. Beides ist toll! Wenn du für ein Tierheim/eine Pflegestelle arbeitest kontaktieren wir dich ggf. um dir erweiterte Rechte zu geben.", verbose_name='Grund für die Registrierung'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='trust_level',
|
||||
field=models.IntegerField(choices=[(1, 'Member'), (2, 'Coordinator'), (3, 'Moderator'), (4, 'Admin')], default=1),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.1 on 2024-11-20 19:25
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0022_alter_user_reason_for_signup_alter_user_trust_level'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='email_notifications',
|
||||
field=models.BooleanField(default=True, verbose_name='Benachrichtigung per E-Mail'),
|
||||
),
|
||||
]
|
||||
18
src/fellchensammlung/migrations/0024_alter_animal_sex.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.1 on 2024-11-21 19:34
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0023_user_email_notifications'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='animal',
|
||||
name='sex',
|
||||
field=models.CharField(choices=[('M_N', 'neutered male'), ('M', 'male'), ('F_N', 'neutered female'), ('F', 'female'), ('I', 'intersex')], max_length=20),
|
||||
),
|
||||
]
|
||||
18
src/fellchensammlung/migrations/0025_alter_animal_sex.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.1 on 2024-11-21 19:41
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0024_alter_animal_sex'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='animal',
|
||||
name='sex',
|
||||
field=models.CharField(choices=[('F', 'Weiblich'), ('M', 'Männlich'), ('M_N', 'Männlich, kastriert'), ('F_N', 'Weiblich Kastriert'), ('I', 'Intersex')], max_length=20),
|
||||
),
|
||||
]
|
||||
18
src/fellchensammlung/migrations/0026_alter_animal_sex.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.1 on 2024-11-21 21:49
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0025_alter_animal_sex'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='animal',
|
||||
name='sex',
|
||||
field=models.CharField(choices=[('F', 'Weiblich'), ('M', 'Männlich'), ('M_N', 'Männlich, kastriert'), ('F_N', 'Weiblich, kastriert'), ('I', 'Intergeschlechtlich')], max_length=20),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,27 @@
|
||||
# Generated by Django 5.1.1 on 2024-12-14 07:57
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0026_alter_animal_sex'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='animal',
|
||||
name='species',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='fellchensammlung.species', verbose_name='Tierart'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AndoptionNoticeNotification',
|
||||
fields=[
|
||||
('basenotification_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='fellchensammlung.basenotification')),
|
||||
('adoption_notice', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.adoptionnotice', verbose_name='Vermittlung')),
|
||||
],
|
||||
bases=('fellchensammlung.basenotification',),
|
||||
),
|
||||
]
|
||||
25
src/fellchensammlung/migrations/0028_searchsubscription.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 5.1.1 on 2024-12-26 15:56
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0027_alter_animal_species_andoptionnoticenotification'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SearchSubscription',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('sex', models.CharField(choices=[('F', 'Weiblich'), ('M', 'Männlich'), ('M_N', 'Männlich, kastriert'), ('F_N', 'Weiblich, kastriert'), ('I', 'Intergeschlechtlich')], max_length=20)),
|
||||
('radius', models.IntegerField(choices=[(20, '20 km'), (50, '50 km'), (100, '100 km'), (200, '200 km'), (500, '500 km')])),
|
||||
('location', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='fellchensammlung.location')),
|
||||
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 5.1.4 on 2024-12-31 10:37
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0028_searchsubscription'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameModel(
|
||||
old_name='AndoptionNoticeNotification',
|
||||
new_name='AdoptionNoticeNotification',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='searchsubscription',
|
||||
name='sex',
|
||||
field=models.CharField(choices=[('F', 'Weiblich'), ('M', 'Männlich'), ('M_N', 'Männlich, kastriert'), ('F_N', 'Weiblich Kastriert'), ('I', 'Intergeschlechtlich'), ('A', 'Alle')], max_length=20),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.4 on 2024-12-31 12:23
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0029_rename_andoptionnoticenotification_adoptionnoticenotification_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='searchsubscription',
|
||||
old_name='radius',
|
||||
new_name='max_distance',
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 5.1.4 on 2024-12-31 12:42
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0030_rename_radius_searchsubscription_max_distance'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='searchsubscription',
|
||||
name='location',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='fellchensammlung.location'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='searchsubscription',
|
||||
name='max_distance',
|
||||
field=models.IntegerField(choices=[(20, '20 km'), (50, '50 km'), (100, '100 km'), (200, '200 km'), (500, '500 km')], null=True),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 5.1.4 on 2025-01-01 22:04
|
||||
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0031_alter_searchsubscription_location_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='searchsubscription',
|
||||
name='created_at',
|
||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='searchsubscription',
|
||||
name='updated_at',
|
||||
field=models.DateTimeField(auto_now=True),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.1.4 on 2025-01-05 18:18
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0032_searchsubscription_created_at_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='rescueorganization',
|
||||
name='external_object_identifier',
|
||||
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='External Object Identifier'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rescueorganization',
|
||||
name='external_source_identifier',
|
||||
field=models.CharField(blank=True, choices=[('OSM', 'Open Street Map')], max_length=200, null=True, verbose_name='External Source Identifier'),
|
||||
),
|
||||
]
|
||||
23
src/fellchensammlung/migrations/0034_speciesspecificurl.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.1.4 on 2025-01-05 19:36
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0033_rescueorganization_external_object_identifier_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SpeciesSpecificURL',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('url', models.URLField(verbose_name='Tierartspezifische URL')),
|
||||
('rescues_organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.rescueorganization', verbose_name='Tierschutzorganisation')),
|
||||
('species', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.species', verbose_name='Tierart')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 5.1.4 on 2025-01-11 12:58
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0034_speciesspecificurl'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='image',
|
||||
name='alt_text',
|
||||
field=models.TextField(max_length=2000, verbose_name='Alternativtext'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='report',
|
||||
name='reported_broken_rules',
|
||||
field=models.ManyToManyField(to='fellchensammlung.rule', verbose_name='Regeln gegen die verstoßen wurde'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='report',
|
||||
name='user_comment',
|
||||
field=models.TextField(blank=True, verbose_name='Kommentar/Zusätzliche Information'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.4 on 2025-01-14 06:28
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0035_alter_image_alt_text_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='basenotification',
|
||||
name='read_at',
|
||||
field=models.DateTimeField(blank=True, null=True, verbose_name='Gelesen am'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.4 on 2025-01-14 06:28
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0036_basenotification_read_at'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='basenotification',
|
||||
name='title',
|
||||
field=models.CharField(max_length=100, verbose_name='Titel'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,20 @@
|
||||
# Generated by Django 5.1.4 on 2025-03-09 08:31
|
||||
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0037_alter_basenotification_title'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='rescueorganization',
|
||||
name='last_checked',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Datum der letzten Prüfung'),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.4 on 2025-03-09 16:44
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0038_rescueorganization_last_checked'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='rescueorganization',
|
||||
name='last_checked',
|
||||
field=models.DateTimeField(auto_now_add=True, verbose_name='Datum der letzten Prüfung'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.4 on 2025-03-20 23:27
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0039_alter_rescueorganization_last_checked'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='rescueorganization',
|
||||
name='allows_using_materials',
|
||||
field=models.CharField(choices=[('allowed', 'Usage allowed'), ('requested', 'Usage requested'), ('denied', 'Usage denied'), ('other', "It's complicated"), ('not_asked', 'Not asked')], default='not_asked', max_length=200, verbose_name='Erlaubt Nutzung von Inhalten'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,42 @@
|
||||
# Generated by Django 5.1.4 on 2025-04-06 06:37
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0040_alter_rescueorganization_allows_using_materials'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='location',
|
||||
name='city',
|
||||
field=models.CharField(blank=True, max_length=200, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='location',
|
||||
name='country',
|
||||
field=models.CharField(blank=True, help_text='Standardisierter Ländercode nach ISO 3166-1 ALPHA-2', max_length=2, null=True, verbose_name='Ländercode'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='location',
|
||||
name='housenumber',
|
||||
field=models.CharField(blank=True, max_length=20, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='location',
|
||||
name='postcode',
|
||||
field=models.CharField(blank=True, max_length=20, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='location',
|
||||
name='street',
|
||||
field=models.CharField(blank=True, max_length=200, null=True),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='rescueorganization',
|
||||
unique_together={('external_object_identifier', 'external_source_identifier')},
|
||||
),
|
||||
]
|
||||
18
src/fellchensammlung/migrations/0042_location_county.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.4 on 2025-04-24 17:13
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0041_location_city_location_country_location_housenumber_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='location',
|
||||
name='county',
|
||||
field=models.CharField(blank=True, max_length=200, null=True),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.4 on 2025-04-24 17:41
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0042_location_county'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='location',
|
||||
old_name='country',
|
||||
new_name='countrycode',
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.4 on 2025-04-26 22:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0043_rename_country_location_countrycode'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='location',
|
||||
name='place_id',
|
||||
field=models.CharField(max_length=200),
|
||||
),
|
||||
]
|
||||
23
src/fellchensammlung/migrations/0045_importantlocation.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.1.4 on 2025-04-27 11:31
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0044_alter_location_place_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ImportantLocation',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('slug', models.SlugField(unique=True)),
|
||||
('name', models.CharField(max_length=200)),
|
||||
('location', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.location')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.1.4 on 2025-04-27 11:51
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0045_importantlocation'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='importantlocation',
|
||||
name='location',
|
||||
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.location'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 5.2.1 on 2025-05-23 16:07
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0046_alter_importantlocation_location'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='adoptionnotice',
|
||||
name='further_information',
|
||||
field=models.URLField(blank=True, help_text='Verlinke hier die Quelle der Vermittlung (z.B. die Website des Tierheims', null=True, verbose_name='Link zu mehr Informationen'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adoptionnotice',
|
||||
name='name',
|
||||
field=models.CharField(max_length=200, verbose_name='Titel der Vermittlung'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rescueorganization',
|
||||
name='location_string',
|
||||
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Ort der Organisation'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.1 on 2025-06-19 15:51
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0047_alter_adoptionnotice_further_information_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='adoptionnotice',
|
||||
name='further_information',
|
||||
field=models.URLField(blank=True, help_text='Verlinke hier die Quelle der Vermittlung (z.B. die Website des Tierheims)', null=True, verbose_name='Link zu mehr Informationen'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.1 on 2025-06-19 21:26
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0048_alter_adoptionnotice_further_information'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='rescueorganization',
|
||||
name='exclude_from_check',
|
||||
field=models.BooleanField(default=False, help_text='Organisation von der manuellen Überprüfung ausschließen, z.B. weil Tiere nicht online geführt werden', verbose_name='Von Prüfung ausschließen'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.1 on 2025-06-20 16:52
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0049_rescueorganization_exclude_from_check'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='speciesspecificurl',
|
||||
old_name='rescues_organization',
|
||||
new_name='rescue_organization',
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,27 @@
|
||||
# Generated by Django 5.2.1 on 2025-07-03 09:28
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0050_rename_rescues_organization_speciesspecificurl_rescue_organization'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='rescueorganization',
|
||||
name='parent_org',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='fellchensammlung.rescueorganization'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SpeciesSpecialization',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('rescue_organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.rescueorganization', verbose_name='Tierschutzorganisation')),
|
||||
('species', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.species', verbose_name='Tierart')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,54 @@
|
||||
# Generated by Django 5.2.1 on 2025-07-11 09:01
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0051_rescueorganization_parent_org_speciesspecialization'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='basenotification',
|
||||
name='user',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='commentnotification',
|
||||
name='basenotification_ptr',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='commentnotification',
|
||||
name='comment',
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Notification',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('read_at', models.DateTimeField(blank=True, null=True, verbose_name='Gelesen am')),
|
||||
('notification_type', models.CharField(choices=[('new_user', 'Useraccount wurde erstellt'), ('new_report_an', 'Vermittlung wurde gemeldet'), ('new_report_comment', 'Kommentar wurde gemeldet'), ('an_is_to_be_checked', 'Vermittlung muss überprüft werden'), ('an_was_deactivated', 'Vermittlung wurde deaktiviert'), ('an_for_search_found', 'Vermittlung für Suche gefunden')], max_length=200, verbose_name='Benachrichtigungsgrund')),
|
||||
('title', models.CharField(max_length=100, verbose_name='Titel')),
|
||||
('text', models.TextField(verbose_name='Inhalt')),
|
||||
('read', models.BooleanField(default=False)),
|
||||
('adoption_notice', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.adoptionnotice', verbose_name='Vermittlung')),
|
||||
('comment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.comment', verbose_name='Antwort')),
|
||||
('report', models.ForeignKey(help_text='Report auf den sich die Benachrichtigung bezieht.', on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.report', verbose_name='Report')),
|
||||
('user_related', models.ForeignKey(help_text='Useraccount auf den sich die Benachrichtigung bezieht.', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Verwandter Useraccount')),
|
||||
('user_to_notify', models.ForeignKey(help_text='Useraccount der Benachrichtigt wird', on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL, verbose_name='Nutzer*in')),
|
||||
],
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='AdoptionNoticeNotification',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='BaseNotification',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='CommentNotification',
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,40 @@
|
||||
# Generated by Django 5.2.1 on 2025-07-11 09:10
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0052_remove_basenotification_user_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='notification',
|
||||
name='adoption_notice',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.adoptionnotice', verbose_name='Vermittlung'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='notification',
|
||||
name='notification_type',
|
||||
field=models.CharField(choices=[('new_user', 'Useraccount wurde erstellt'), ('new_report_an', 'Vermittlung wurde gemeldet'), ('new_report_comment', 'Kommentar wurde gemeldet'), ('an_is_to_be_checked', 'Vermittlung muss überprüft werden'), ('an_was_deactivated', 'Vermittlung wurde deaktiviert'), ('an_for_search_found', 'Vermittlung für Suche gefunden'), ('new_comment', 'Neuer Kommentar')], max_length=200, verbose_name='Benachrichtigungsgrund'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='notification',
|
||||
name='report',
|
||||
field=models.ForeignKey(blank=True, help_text='Report auf den sich die Benachrichtigung bezieht.', null=True, on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.report', verbose_name='Report'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='notification',
|
||||
name='user_related',
|
||||
field=models.ForeignKey(blank=True, help_text='Useraccount auf den sich die Benachrichtigung bezieht.', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Verwandter Useraccount'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='notification',
|
||||
name='user_to_notify',
|
||||
field=models.ForeignKey(help_text='Useraccount der Benachrichtigt wird', on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL, verbose_name='Nutzer*in'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.2.1 on 2025-07-11 11:47
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0053_alter_notification_adoption_notice_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='notification',
|
||||
name='comment',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.comment', verbose_name='Antwort'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 5.2.1 on 2025-07-13 10:54
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0054_alter_notification_comment'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='rescueorganization',
|
||||
name='ongoing_communication',
|
||||
field=models.BooleanField(default=False, help_text='Es findet gerade Kommunikation zwischen Notfellchen und der Organisation statt.', verbose_name='In aktiver Kommunikation'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='notification',
|
||||
name='user_to_notify',
|
||||
field=models.ForeignKey(help_text='Useraccount der Benachrichtigt wird', on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL, verbose_name='Empfänger*in'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 5.2.1 on 2025-07-14 05:12
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0055_rescueorganization_ongoing_communication_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='rescueorganization',
|
||||
options={'ordering': ['name']},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rescueorganization',
|
||||
name='specializations',
|
||||
field=models.ManyToManyField(to='fellchensammlung.species'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,16 @@
|
||||
# Generated by Django 5.2.1 on 2025-07-14 05:15
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0056_alter_rescueorganization_options_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='SpeciesSpecialization',
|
||||
),
|
||||
]
|
||||
25
src/fellchensammlung/migrations/0058_socialmediapost.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 5.2.1 on 2025-07-19 17:48
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0057_delete_speciesspecialization'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SocialMediaPost',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateField(default=django.utils.timezone.now, verbose_name='Erstellt am')),
|
||||
('platform', models.CharField(choices=[('fediverse', 'Fediverse')], max_length=255, verbose_name='Social Media Platform')),
|
||||
('url', models.URLField(verbose_name='URL')),
|
||||
('adoption_notice', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.adoptionnotice', verbose_name='Vermittlung')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.2.1 on 2025-08-02 09:33
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0058_socialmediapost'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='rescueorganization',
|
||||
name='twenty_id',
|
||||
field=models.UUIDField(blank=True, help_text='ID der der Organisation in Twenty', null=True, verbose_name='Twenty-ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rescueorganization',
|
||||
name='specializations',
|
||||
field=models.ManyToManyField(blank=True, to='fellchensammlung.species'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,87 @@
|
||||
# Generated by Django 5.2.1 on 2025-08-30 21:49
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0059_rescueorganization_twenty_id_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='adoptionnotice',
|
||||
options={'permissions': [('create_active_adoption_notice', 'Can create an active adoption notice')], 'verbose_name': 'Vermittlung', 'verbose_name_plural': 'Vermittlungen'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='adoptionnoticestatus',
|
||||
options={'verbose_name': 'Vermittlungsstatus', 'verbose_name_plural': 'Vermittlungsstati'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='animal',
|
||||
options={'verbose_name': 'Tier', 'verbose_name_plural': 'Tiere'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='announcement',
|
||||
options={'verbose_name': 'Banner', 'verbose_name_plural': 'Banner'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='comment',
|
||||
options={'verbose_name': 'Kommentar', 'verbose_name_plural': 'Kommentare'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='image',
|
||||
options={'verbose_name': 'Bild', 'verbose_name_plural': 'Bilder'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='importantlocation',
|
||||
options={'verbose_name': 'Wichtiger Standort', 'verbose_name_plural': 'Wichtige Standorte'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='location',
|
||||
options={'verbose_name': 'Standort', 'verbose_name_plural': 'Standorte'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='moderationaction',
|
||||
options={'verbose_name': 'Moderationsaktion', 'verbose_name_plural': 'Moderationsaktionen'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='notification',
|
||||
options={'verbose_name': 'Benachrichtigung', 'verbose_name_plural': 'Benachrichtigungen'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='report',
|
||||
options={'verbose_name': 'Meldung', 'verbose_name_plural': 'Meldungen'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='rescueorganization',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Tierschutzorganisation', 'verbose_name_plural': 'Tierschutzorganisationen'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='rule',
|
||||
options={'verbose_name': 'Regel', 'verbose_name_plural': 'Regeln'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='searchsubscription',
|
||||
options={'verbose_name': 'Abonnierte Suche', 'verbose_name_plural': 'Abonnierte Suchen'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='speciesspecificurl',
|
||||
options={'verbose_name': 'Tierartspezifische URL', 'verbose_name_plural': 'Tierartspezifische URLs'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='subscriptions',
|
||||
options={'verbose_name': 'Abonnement', 'verbose_name_plural': 'Abonnements'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='timestamp',
|
||||
options={'verbose_name': 'Zeitstempel', 'verbose_name_plural': 'Zeitstempel'},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='adoptionnotice',
|
||||
name='adoption_notice_status',
|
||||
field=models.TextField(choices=[('active_searching', 'Searching'), ('active_interested', 'Interested'), ('awaiting_action_waiting_for_review', 'Waiting for review'), ('awaiting_action_needs_additional_info', 'Needs additional info'), ('closed_successful_with_notfellchen', 'Successful (with Notfellchen)'), ('closed_successful_without_notfellchen', 'Successful (without Notfellchen)'), ('closed_animal_died', 'Animal died'), ('closed_for_other_adoption_notice', 'Closed for other adoption notice'), ('closed_not_open_for_adoption_anymore', 'Not open for adoption anymore'), ('closed_other', 'Other (closed)'), ('disabled_against_the_rules', 'Against the rules'), ('disabled_unchecked', 'Unchecked'), ('disabled_other', 'Other (disabled)')], default='disabled_other', max_length=64, verbose_name='Status'),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,63 @@
|
||||
# Generated by Django 5.2.1 on 2025-08-30 21:51
|
||||
import logging
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def map_status(adoption_notice_status):
|
||||
minor = adoption_notice_status.minor_status
|
||||
|
||||
if minor == "searching":
|
||||
return "active_searching"
|
||||
if minor == "interested":
|
||||
return "active_interested"
|
||||
|
||||
if minor == "waiting_for_review":
|
||||
return "awaiting_action_waiting_for_review"
|
||||
if minor == "needs_additional_info":
|
||||
return "awaiting_action_needs_additional_info"
|
||||
|
||||
if minor == "successful_with_notfellchen":
|
||||
return "closed_successful_with_notfellchen"
|
||||
if minor == "successful_without_notfellchen":
|
||||
return "closed_successful_without_notfellchen"
|
||||
if minor == "animal_died":
|
||||
return "closed_animal_died"
|
||||
if minor == "closed_for_other_adoption_notice":
|
||||
return "closed_for_other_adoption_notice"
|
||||
if minor == "not_open_for_adoption_anymore":
|
||||
return "closed_not_open_for_adoption_anymore"
|
||||
if minor == "other":
|
||||
return "closed_other"
|
||||
|
||||
if minor == "against_the_rules":
|
||||
return "disabled_against_the_rules"
|
||||
if minor == "unchecked":
|
||||
return "disabled_unchecked"
|
||||
if minor in ["missing_information", "technical_error"]:
|
||||
return "disabled_other"
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def migrate_status(apps, schema_editor):
|
||||
# We can't import the model directly as it may be a newer
|
||||
# version than this migration expects. We use the historical version.
|
||||
AdoptionNoticeStatus = apps.get_model("fellchensammlung", "AdoptionNoticeStatus")
|
||||
AdoptionNotice = apps.get_model("fellchensammlung", "AdoptionNotice")
|
||||
for ans in AdoptionNoticeStatus.objects.all():
|
||||
adoption_notice = AdoptionNotice.objects.get(id=ans.adoption_notice.id)
|
||||
new_status = map_status(ans)
|
||||
logging.debug(f"{ans.minor_status} -> {new_status}")
|
||||
adoption_notice.adoption_notice_status = map_status(ans)
|
||||
adoption_notice.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('fellchensammlung', '0060_alter_adoptionnotice_options_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(migrate_status),
|
||||
]
|
||||
@@ -0,0 +1,21 @@
|
||||
# Generated by Django 5.2.1 on 2025-08-30 22:50
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0061_datamigration_status_model_to_field'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='adoptionnotice',
|
||||
name='adoption_notice_status',
|
||||
field=models.TextField(choices=[('active_searching', 'Searching'), ('active_interested', 'Interested'), ('awaiting_action_waiting_for_review', 'Waiting for review'), ('awaiting_action_needs_additional_info', 'Needs additional info'), ('closed_successful_with_notfellchen', 'Successful (with Notfellchen)'), ('closed_successful_without_notfellchen', 'Successful (without Notfellchen)'), ('closed_animal_died', 'Animal died'), ('closed_for_other_adoption_notice', 'Closed for other adoption notice'), ('closed_not_open_for_adoption_anymore', 'Not open for adoption anymore'), ('closed_link_to_more_info_not_reachable', 'Der Link zu weiteren Informationen ist nicht mehr erreichbar.'), ('closed_other', 'Other (closed)'), ('disabled_against_the_rules', 'Against the rules'), ('disabled_unchecked', 'Unchecked'), ('disabled_other', 'Other (disabled)')], max_length=64, verbose_name='Status'),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='AdoptionNoticeStatus',
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.1 on 2025-09-05 14:04
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0062_alter_adoptionnotice_adoption_notice_status_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='adoptionnotice',
|
||||
name='adoption_process',
|
||||
field=models.TextField(blank=True, choices=[('contact_person_in_an', 'Kontaktiere die Person im Vermittlungstext')], max_length=64, null=True, verbose_name='Adoptionsprozess'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,140 @@
|
||||
# Generated by Django 5.2.1 on 2025-09-06 11:11
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0063_adoptionnotice_adoption_process'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='animal',
|
||||
name='name',
|
||||
field=models.CharField(max_length=200, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='animal',
|
||||
name='photos',
|
||||
field=models.ManyToManyField(blank=True, to='fellchensammlung.image', verbose_name='Fotos'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='animal',
|
||||
name='sex',
|
||||
field=models.CharField(choices=[('F', 'Weiblich'), ('M', 'Männlich'), ('M_N', 'Männlich, kastriert'), ('F_N', 'Weiblich, kastriert'), ('I', 'Intergeschlechtlich')], max_length=20, verbose_name='Geschlecht'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='comment',
|
||||
name='adoption_notice',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.adoptionnotice', verbose_name='Vermittlung'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='image',
|
||||
name='alt_text',
|
||||
field=models.TextField(help_text='Beschreibe das Bild für blinde und sehbehinderte Menschen', max_length=2000, verbose_name='Alternativtext'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='location',
|
||||
name='city',
|
||||
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Stadt'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='location',
|
||||
name='county',
|
||||
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Landkreis'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='location',
|
||||
name='housenumber',
|
||||
field=models.CharField(blank=True, max_length=20, null=True, verbose_name='Hausnummer'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='location',
|
||||
name='latitude',
|
||||
field=models.FloatField(verbose_name='Breitengrad'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='location',
|
||||
name='longitude',
|
||||
field=models.FloatField(verbose_name='Längengrad'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='location',
|
||||
name='postcode',
|
||||
field=models.CharField(blank=True, max_length=20, null=True, verbose_name='Postleitzahl'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='location',
|
||||
name='street',
|
||||
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Straße'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='notification',
|
||||
name='user_to_notify',
|
||||
field=models.ForeignKey(help_text='Useraccount der benachrichtigt wird', on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL, verbose_name='Empfänger*in'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='report',
|
||||
name='created_at',
|
||||
field=models.DateTimeField(auto_now_add=True, verbose_name='Erstellt am'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='report',
|
||||
name='updated_at',
|
||||
field=models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert am'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rule',
|
||||
name='created_at',
|
||||
field=models.DateTimeField(auto_now_add=True, verbose_name='Erstellt am'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rule',
|
||||
name='language',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='fellchensammlung.language', verbose_name='Sprache'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rule',
|
||||
name='rule_identifier',
|
||||
field=models.CharField(help_text='Ein eindeutiger Identifikator der Regel. Ein Regelobjekt derselben Regel in einer anderen Sprache muss den gleichen Identifikator haben', max_length=24, verbose_name='Regel-ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rule',
|
||||
name='rule_text',
|
||||
field=models.TextField(verbose_name='Regeltext'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rule',
|
||||
name='updated_at',
|
||||
field=models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert am'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='searchsubscription',
|
||||
name='created_at',
|
||||
field=models.DateTimeField(auto_now_add=True, verbose_name='Erstellt am'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='searchsubscription',
|
||||
name='sex',
|
||||
field=models.CharField(choices=[('F', 'Weiblich'), ('M', 'Männlich'), ('M_N', 'Männlich, kastriert'), ('F_N', 'Weiblich Kastriert'), ('I', 'Intergeschlechtlich'), ('A', 'Alle')], max_length=20, verbose_name='Geschlecht'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='searchsubscription',
|
||||
name='updated_at',
|
||||
field=models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert am'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='subscriptions',
|
||||
name='adoption_notice',
|
||||
field=models.ForeignKey(help_text='Vermittlung die abonniert wurde', on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.adoptionnotice', verbose_name='Vermittlung'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='text',
|
||||
name='title',
|
||||
field=models.CharField(max_length=100, verbose_name='Titel'),
|
||||
),
|
||||
]
|
||||
18
src/fellchensammlung/migrations/0065_species_slug.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.1 on 2025-09-06 13:02
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0064_alter_animal_name_alter_animal_photos_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='species',
|
||||
name='slug',
|
||||
field=models.SlugField(null=True, unique=True, verbose_name='Slug'),
|
||||
),
|
||||
]
|
||||
20
src/fellchensammlung/migrations/0066_add_slug_to_species.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# Generated by Django 5.2.1 on 2025-09-06 13:05
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def migrate_slug(apps, schema_editor):
|
||||
Species = apps.get_model("fellchensammlung", "Species")
|
||||
for species in Species.objects.all():
|
||||
species.slug = f"species-{species.id}"
|
||||
species.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('fellchensammlung', '0065_species_slug'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(migrate_slug),
|
||||
]
|
||||