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 | # Database | ||||||
| notfellchen | notfellchen | ||||||
|  | *.sq3 | ||||||
|  |  | ||||||
|  | # Geojson from imports | ||||||
|  | *.geojson | ||||||
|  |  | ||||||
| # Media storage | # Media storage | ||||||
| static | /static | ||||||
| media | media | ||||||
|  |  | ||||||
|  | # Compiled CSS | ||||||
|  | /src/fellchensammlung/static/fellchensammlung/css/main.css | ||||||
|  | /src/fellchensammlung/static/fellchensammlung/css/main.css.map | ||||||
|  |  | ||||||
| # Byte-compiled / optimized / DLL files | # Byte-compiled / optimized / DLL files | ||||||
| __pycache__/ | __pycache__/ | ||||||
| @@ -161,3 +168,4 @@ dmypy.json | |||||||
|  |  | ||||||
| # Cython debug symbols | # Cython debug symbols | ||||||
| cython_debug/ | 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 | FROM python:3.11-slim | ||||||
| # Use 3.11 to avoid django.core.exceptions.ImproperlyConfigured: Error loading psycopg2 or psycopg module | # 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 | ENV DOCKER_BUILD=true | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										93
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @@ -2,7 +2,7 @@ | |||||||
|  |  | ||||||
| [notfellchen.org](https://notfellchen.org) ist eine Sammelstelle für Tier-Vermittlungen. Die Idee entstand, da in der | [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. | 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. | wollen Informationen einfach finden und nicht bereits in jeder Gruppe sein müssen. | ||||||
|  |  | ||||||
| Wir nehmen Angebote auf die | Wir nehmen Angebote auf die | ||||||
| @@ -45,21 +45,62 @@ There is a system for customizing texts in Notfellchen. Not every change of a te | |||||||
| 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. | 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              | | | Textcode                | Location              | | ||||||
| |---------------------|----------| | |-------------------------|-----------------------| | ||||||
| | `how_to`                | Index                 | | | `how_to`                | Index                 | | ||||||
| | `introduction`          | Index                 | | | `introduction`          | Index                 | | ||||||
| | `privacy_statement`     | About                 | | | `privacy_statement`     | About                 | | ||||||
| | `terms_of_service`      | About                 | | | `terms_of_service`      | About                 | | ||||||
| | `imprint`               | About                 | | | `imprint`               | About                 | | ||||||
| | `about_us`              | About                 | | | `about_us`              | About                 | | ||||||
|  | | `external_site_warning` | External Site Warning | | ||||||
| | Any rule                | About                 | | | Any rule                | About                 | | ||||||
|  |  | ||||||
| # Developer Notes | # 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 | 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 | 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. | 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 | ## Docker | ||||||
|  |  | ||||||
| Build latest image | Build latest image | ||||||
| @@ -76,20 +117,37 @@ docker push moanos/notfellchen:latest | |||||||
| docker run -p8000:7345 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 | ||||||
|  |  | ||||||
| Geocoding services (search map data by name, address or postcode) are provided via the | 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 | either [Nominatim](https://nominatim.org/) or [photon](https://github.com/komoot/photon) API, powered by [OpenStreetMap](https://openstreetmap.org) data. | ||||||
| a selfhosted Nominatim instance to avoid overburdening the publicly hosted instance. Due to ressource constraints | Notfellchen uses a selfhosted Photon instance to avoid overburdening the publicly hosted instance. | ||||||
| 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 |  | ||||||
|  |  | ||||||
| ## Maps | ## Maps | ||||||
|  |  | ||||||
| The map on the main homepage is powered by [Versatiles](https://versatiles.org), and rendered using [Maplibre](https://maplibre.org/). | 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 | ## Translation | ||||||
|  |  | ||||||
| @@ -124,3 +182,20 @@ Start beat | |||||||
| ```zsh | ```zsh | ||||||
|  celery -A notfellchen.celery beat |  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 | 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:: | .. 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 | 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. | 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 | 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. | 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. | An application can then send this token in the request header for authorization. | ||||||
|  |  | ||||||
| .. code-block:: | .. code-block:: | ||||||
|     $ curl -X GET http://notfellchen.org/api/adoption_notice -H 'Authorization: Token 49b39856955dc6e5cc04365498d4ad30ea3aed78' |     $ 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. | 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. | Add the following to your `notfellchen.cfg` and adjust the URL to match your check. | ||||||
| .. code:: | .. code:: | ||||||
|  |  | ||||||
|   [monitoring] |   [monitoring] | ||||||
|   healthchecks_url=https://health.example.org/ping/5fa7c9b2-753a-4cb3-bcc9-f982f5bc68e8 |   healthchecks_url=https://health.example.org/ping/5fa7c9b2-753a-4cb3-bcc9-f982f5bc68e8 | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								docs/conf.py
									
									
									
									
									
								
							
							
						
						| @@ -16,6 +16,10 @@ | |||||||
| # import sys | # import sys | ||||||
| # sys.path.insert(0, os.path.abspath('.')) | # sys.path.insert(0, os.path.abspath('.')) | ||||||
|  |  | ||||||
|  | import sys | ||||||
|  | from pathlib import Path | ||||||
|  |  | ||||||
|  | sys.path.append(str(Path('_ext').resolve())) | ||||||
|  |  | ||||||
| # -- Project information ----------------------------------------------------- | # -- Project information ----------------------------------------------------- | ||||||
|  |  | ||||||
| @@ -28,7 +32,6 @@ version = '' | |||||||
| # The full version, including alpha/beta/rc tags | # The full version, including alpha/beta/rc tags | ||||||
| release = '0.2.0' | release = '0.2.0' | ||||||
|  |  | ||||||
|  |  | ||||||
| # -- General configuration --------------------------------------------------- | # -- General configuration --------------------------------------------------- | ||||||
|  |  | ||||||
| # If your documentation needs a minimal Sphinx version, state it here. | # If your documentation needs a minimal Sphinx version, state it here. | ||||||
| @@ -40,6 +43,7 @@ release = '0.2.0' | |||||||
| # ones. | # ones. | ||||||
| extensions = [ | extensions = [ | ||||||
|     'sphinx.ext.ifconfig', |     'sphinx.ext.ifconfig', | ||||||
|  |     'drawio' | ||||||
| ] | ] | ||||||
|  |  | ||||||
| # Add any paths that contain templates here, relative to this directory. | # 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. | # The name of the Pygments (syntax highlighting) style to use. | ||||||
| pygments_style = None | pygments_style = None | ||||||
|  |  | ||||||
|  |  | ||||||
| # -- Options for HTML output ------------------------------------------------- | # -- Options for HTML output ------------------------------------------------- | ||||||
|  |  | ||||||
| # The theme to use for HTML and HTML Help pages.  See the documentation for | # 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. | # Output file base name for HTML help builder. | ||||||
| htmlhelp_basename = 'notfellchen' | htmlhelp_basename = 'notfellchen' | ||||||
|  |  | ||||||
|  |  | ||||||
| # -- Options for LaTeX output ------------------------------------------------ | # -- Options for LaTeX output ------------------------------------------------ | ||||||
|  |  | ||||||
| latex_elements = { | latex_elements = { | ||||||
| @@ -133,7 +135,6 @@ latex_documents = [ | |||||||
|      'Julian-Samuel Gebühr', 'manual'), |      'Julian-Samuel Gebühr', 'manual'), | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  |  | ||||||
| # -- Options for manual page output ------------------------------------------ | # -- Options for manual page output ------------------------------------------ | ||||||
|  |  | ||||||
| # One entry per manual page. List of tuples | # One entry per manual page. List of tuples | ||||||
| @@ -143,7 +144,6 @@ man_pages = [ | |||||||
|      [author], 1) |      [author], 1) | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  |  | ||||||
| # -- Options for Texinfo output ---------------------------------------------- | # -- Options for Texinfo output ---------------------------------------------- | ||||||
|  |  | ||||||
| # Grouping the document tree into Texinfo files. List of tuples | # Grouping the document tree into Texinfo files. List of tuples | ||||||
| @@ -155,7 +155,6 @@ texinfo_documents = [ | |||||||
|      'Miscellaneous'), |      'Miscellaneous'), | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  |  | ||||||
| # -- Options for Epub output ------------------------------------------------- | # -- Options for Epub output ------------------------------------------------- | ||||||
|  |  | ||||||
| # Bibliographic Dublin Core info. | # Bibliographic Dublin Core info. | ||||||
| @@ -173,5 +172,4 @@ epub_title = project | |||||||
| # A list of files that should not be packed into the epub file. | # A list of files that should not be packed into the epub file. | ||||||
| epub_exclude_files = ['search.html'] | epub_exclude_files = ['search.html'] | ||||||
|  |  | ||||||
|  |  | ||||||
| # -- Extension configuration ------------------------------------------------- | # -- Extension configuration ------------------------------------------------- | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ Report a bug | |||||||
| ^^^^^^^^^^^^ | ^^^^^^^^^^^^ | ||||||
|  |  | ||||||
| To report a bug, file an issue on `Github | 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: | Try to include the following information: | ||||||
|  |  | ||||||
| @@ -29,7 +29,7 @@ To contribute simply clone the directory, make your changes and file a | |||||||
| pull request. | pull request. | ||||||
|  |  | ||||||
| If you want to know what can be done, have a look at the current `Github | 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! | 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 | 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. | is made. Notfellchen follows `Semantic Versioning <https://semver.org/>`_. | ||||||
| Major releases are yet to be determined. |  | ||||||
|  |  | ||||||
| What should be done before a release? | What should be done before a release? | ||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||||||
| @@ -14,7 +13,7 @@ What should be done before a release? | |||||||
| Tested basic functions | Tested basic functions | ||||||
| ###################### | ###################### | ||||||
|  |  | ||||||
| Run :command:`pytest` | Run :command:`nf test src` | ||||||
|  |  | ||||||
| Test upgrade on a copy of a production database | 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 tag -a v1.0.0 -m "Releasing version v1.0.0" | ||||||
|     git push origin 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 | .. 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 | 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 | E-Mail | ||||||
| ++++++ | ++++++ | ||||||
|  |  | ||||||
| Mit während deiner :doc:`registrierung` gibst du eine E-Mail Addresse an. | 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. | ||||||
| Benachrichtigungen senden wir per Mail - du kannst das jederzeit in den Einstellungen deaktivieren. |  | ||||||
							
								
								
									
										
											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:: | .. toctree:: | ||||||
|    :maxdepth: 2 |    :maxdepth: 2 | ||||||
|    :caption: Inhalt: |    :caption: Inhalt: | ||||||
|  |  | ||||||
|  |    erste-schritte.rst | ||||||
|    registrierung.rst |    registrierung.rst | ||||||
|    vermittlungen.rst |    vermittlungen.rst | ||||||
|    moderationskonzept.rst |    moderationskonzept.rst | ||||||
|    benachrichtigungen.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 | ||||||
| ============= | ============= | ||||||
|  |  | ||||||
| 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. | 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. | 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. | 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. | 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 | static=./static | ||||||
|  |  | ||||||
| [mail] | [mail] | ||||||
| console-only=true | console_only=true | ||||||
|  |  | ||||||
| [logging] | [logging] | ||||||
| app_log_level=INFO | app_log_level=INFO | ||||||
| django_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,14 +6,14 @@ build-backend = "setuptools.build_meta" | |||||||
|  |  | ||||||
| [project] | [project] | ||||||
| name = "notfellchen" | 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 = [ | authors = [ | ||||||
|     { name = "moanos", email = "julian-samuel@gebuehr.net" }, |     { name = "moanos", email = "julian-samuel@gebuehr.net" }, | ||||||
| ] | ] | ||||||
| maintainers = [ | maintainers = [ | ||||||
|     { name = "moanos", email = "julian-samuel@gebuehr.net" }, |     { name = "moanos", email = "julian-samuel@gebuehr.net" }, | ||||||
| ] | ] | ||||||
| keywords = ["animal", "adoption", "django", "rescue", ] | keywords = ["animal", "adoption", "django", "rescue", "rats" ] | ||||||
| license = { text = "AGPL-3.0-or-later" } | license = { text = "AGPL-3.0-or-later" } | ||||||
| classifiers = [ | classifiers = [ | ||||||
|     "Environment :: Web", |     "Environment :: Web", | ||||||
| @@ -24,22 +24,22 @@ classifiers = [ | |||||||
| ] | ] | ||||||
| dependencies = [ | dependencies = [ | ||||||
|     "Django", |     "Django", | ||||||
|     "coverage", |  | ||||||
|     "codecov", |     "codecov", | ||||||
|     "sphinx", |  | ||||||
|     "sphinx-rtd-theme", |  | ||||||
|     "gunicorn", |     "gunicorn", | ||||||
|     "fontawesomefree", |     "fontawesomefree", | ||||||
|     "whitenoise", |     "whitenoise", | ||||||
|     "model_bakery", |  | ||||||
|     "markdown", |     "markdown", | ||||||
|     "Pillow", |     "Pillow", | ||||||
|     "django-registration", |     "django-registration", | ||||||
|     "psycopg2", |     "psycopg2-binary", | ||||||
|     "django-crispy-forms", |     "django-crispy-forms", | ||||||
|     "crispy-bootstrap4", |     "crispy-bootstrap4", | ||||||
|     "djangorestframework", |     "djangorestframework", | ||||||
|     "celery[redis]" |     "celery[redis]", | ||||||
|  |     "drf-spectacular[sidecar]", | ||||||
|  |     "django-widget-tweaks", | ||||||
|  |     "django-super-deduper", | ||||||
|  |     "django-allauth[mfa]" | ||||||
| ] | ] | ||||||
|  |  | ||||||
| dynamic = ["version", "readme"] | dynamic = ["version", "readme"] | ||||||
| @@ -47,6 +47,13 @@ dynamic = ["version", "readme"] | |||||||
| [project.optional-dependencies] | [project.optional-dependencies] | ||||||
| develop = [ | develop = [ | ||||||
|     "pytest", |     "pytest", | ||||||
|  |     "coverage", | ||||||
|  |     "model_bakery", | ||||||
|  | ] | ||||||
|  | docs = [ | ||||||
|  |     "sphinx", | ||||||
|  |     "sphinx-rtd-theme", | ||||||
|  |     "sphinx-autobuild" | ||||||
| ] | ] | ||||||
|  |  | ||||||
| [project.urls] | [project.urls] | ||||||
|   | |||||||
							
								
								
									
										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 | import csv | ||||||
| from django.contrib import admin |  | ||||||
| from django.utils.html import format_html |  | ||||||
|  |  | ||||||
| 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, \ | 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 _ | ||||||
|  |  | ||||||
|  | from .tools.model_helpers import AdoptionNoticeStatusChoices | ||||||
| class StatusInline(admin.StackedInline): |  | ||||||
|     model = AdoptionNoticeStatus |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @admin.register(AdoptionNotice) | @admin.register(AdoptionNotice) | ||||||
| class AdoptionNoticeAdmin(admin.ModelAdmin): | class AdoptionNoticeAdmin(admin.ModelAdmin): | ||||||
|     search_fields = ("name__icontains", "description__icontains") |     search_fields = ("name__icontains", "description__icontains") | ||||||
|     inlines = [ |     list_filter = ("owner",) | ||||||
|         StatusInline, |     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 | # 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): | def _reported_content_link(obj): | ||||||
| @@ -51,11 +92,19 @@ class ReportAdoptionNoticeAdmin(admin.ModelAdmin): | |||||||
|     reported_content_link.short_description = "Reported Content" |     reported_content_link.short_description = "Reported Content" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class SpeciesSpecificURLInline(admin.StackedInline): | ||||||
|  |     model = SpeciesSpecificURL | ||||||
|  |  | ||||||
|  |  | ||||||
| @admin.register(RescueOrganization) | @admin.register(RescueOrganization) | ||||||
| class RescueOrganizationAdmin(admin.ModelAdmin): | 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_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) | @admin.register(Text) | ||||||
| @@ -63,15 +112,65 @@ class TextAdmin(admin.ModelAdmin): | |||||||
|     search_fields = ("title__icontains", "text_code__icontains",) |     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(Animal) | ||||||
| admin.site.register(Species) | admin.site.register(Species) | ||||||
| admin.site.register(Location) |  | ||||||
| admin.site.register(Rule) | admin.site.register(Rule) | ||||||
| admin.site.register(Image) | admin.site.register(Image) | ||||||
| admin.site.register(ModerationAction) | admin.site.register(ModerationAction) | ||||||
| admin.site.register(Language) | admin.site.register(Language) | ||||||
| admin.site.register(Announcement) | admin.site.register(Announcement) | ||||||
| admin.site.register(AdoptionNoticeStatus) |  | ||||||
| admin.site.register(Subscriptions) | admin.site.register(Subscriptions) | ||||||
| admin.site.register(Log) | admin.site.register(Log) | ||||||
| admin.site.register(Timestamp) | 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 | 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): | 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: |     class Meta: | ||||||
|         model = AdoptionNotice |         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 django.urls import path | ||||||
| from .views import ( | from .views import ( | ||||||
|     AdoptionNoticeApiView |     AdoptionNoticeApiView, | ||||||
|  |     AnimalApiView, RescueOrganizationApiView, AddImageApiView, SpeciesApiView, LocationApiView, | ||||||
|  |     AdoptionNoticeGeoJSONView, RescueOrgGeoJSONView, AdoptionNoticePerOrgApiView | ||||||
| ) | ) | ||||||
|  |  | ||||||
| urlpatterns = [ | 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.views import APIView | ||||||
| from rest_framework.response import Response | from rest_framework.response import Response | ||||||
| from rest_framework import status | from django.db import transaction | ||||||
| from rest_framework import permissions | from fellchensammlung.models import Log, TrustLevel, Location, AdoptionNoticeStatusChoices | ||||||
| from ..models import AdoptionNotice | from fellchensammlung.tasks import post_adoption_notice_save, post_rescue_org_save | ||||||
| from .serializers import AdoptionNoticeSerializer | 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): | 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): |     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() |         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) |         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): |     def post(self, request, *args, **kwargs): | ||||||
|         data = { |         """ | ||||||
|             'name': request.data.get('name'), |         API view to add an adoption notice. | ||||||
|             "searching_since": request.data.get('searching_since'), |         """ | ||||||
|             "description": request.data.get('description'), |         serializer = AdoptionNoticeSerializer(data=request.data, context={'request': request}) | ||||||
|             "organization": request.data.get('organization'), |         if not serializer.is_valid(): | ||||||
|             "further_information": request.data.get('further_information'), |             return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) | ||||||
|             "location_string": request.data.get('location_string'), |  | ||||||
|             "group_only": request.data.get('group_only'), |         adoption_notice = serializer.save(owner=request.user_to_notify) | ||||||
|             "owner": request.data.get('owner') |  | ||||||
|         } |         # Add the location | ||||||
|         serializer = AdoptionNoticeSerializer(data=data) |         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(): |         if serializer.is_valid(): | ||||||
|             serializer.save() |             animal = serializer.save(owner=request.user_to_notify) | ||||||
|             return Response(serializer.data, status=status.HTTP_201_CREATED) |             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) |         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: |         except Permission.DoesNotExist: | ||||||
|             pass |             pass | ||||||
|         post_migrate.connect(ensure_languages, sender=self) |         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 django import forms | ||||||
|  |  | ||||||
| from .models import AdoptionNotice, Animal, Image, ReportAdoptionNotice, ReportComment, ModerationAction, User, Species, \ | from .models import AdoptionNotice, Animal, Image, ReportAdoptionNotice, ReportComment, ModerationAction, User, Species, \ | ||||||
|     Comment |     Comment, SexChoicesWithAll, DistanceChoices, SpeciesSpecificURL, RescueOrganization | ||||||
| from django_registration.forms import RegistrationForm | from django_registration.forms import RegistrationForm | ||||||
| from crispy_forms.helper import FormHelper | from crispy_forms.helper import FormHelper | ||||||
| from crispy_forms.layout import Submit, Layout, Fieldset, HTML, Row, Column, Field, Hidden | from crispy_forms.layout import Submit, Layout, Fieldset, HTML, Row, Column, Field, Hidden | ||||||
| from django.utils.translation import gettext_lazy as _ | from django.utils.translation import gettext_lazy as _ | ||||||
| from notfellchen.settings import MEDIA_URL | 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): | class DateInput(forms.DateInput): | ||||||
| @@ -14,92 +23,46 @@ class DateInput(forms.DateInput): | |||||||
|  |  | ||||||
|  |  | ||||||
| class AdoptionNoticeForm(forms.ModelForm): | class AdoptionNoticeForm(forms.ModelForm): | ||||||
|     def __init__(self, *args, **kwargs): |     template_name = "fellchensammlung/forms/form_snippets.html" | ||||||
|         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) |  | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|         model = AdoptionNotice |         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"] | ||||||
|  |  | ||||||
| class AdoptionNoticeFormWithDateWidget(AdoptionNoticeForm): |  | ||||||
|     class Meta: |  | ||||||
|         model = AdoptionNotice |  | ||||||
|         fields = ['name', "group_only", "further_information", "description", "searching_since", "location_string"] |  | ||||||
|         widgets = { |         widgets = { | ||||||
|             'searching_since': DateInput(), |             'searching_since': DateInput(format=('%Y-%m-%d')), | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class AdoptionNoticeFormAutoAnimal(AdoptionNoticeForm): | ||||||
| class AnimalForm(forms.ModelForm): |  | ||||||
|     def __init__(self, *args, **kwargs): |     def __init__(self, *args, **kwargs): | ||||||
|         if 'in_adoption_notice_creation_flow' in kwargs: |         super(AdoptionNoticeFormAutoAnimal, self).__init__(*args, **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) |  | ||||||
|         self.fields["num_animals"] = forms.fields.IntegerField(min_value=1, max_value=30, label=_("Zahl der Tiere")) |         self.fields["num_animals"] = forms.fields.IntegerField(min_value=1, max_value=30, label=_("Zahl der Tiere")) | ||||||
|         animal_form = AnimalForm() |         animal_form = AnimalForm() | ||||||
|         self.fields["species"] = animal_form.fields["species"] |         self.fields["species"] = animal_form.fields["species"] | ||||||
|         self.fields["sex"] = animal_form.fields["sex"] |         self.fields["sex"] = animal_form.fields["sex"] | ||||||
|         self.fields["date_of_birth"] = animal_form.fields["date_of_birth"] |         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): | class ImageForm(forms.ModelForm): | ||||||
| @@ -113,11 +76,21 @@ class ImageForm(forms.ModelForm): | |||||||
|         self.helper.form_id = 'form-animal-photo' |         self.helper.form_id = 'form-animal-photo' | ||||||
|         self.helper.form_class = 'card' |         self.helper.form_class = 'card' | ||||||
|         self.helper.form_method = 'post' |         self.helper.form_method = 'post' | ||||||
|  |  | ||||||
|         if in_flow: |         if in_flow: | ||||||
|             self.helper.add_input(Submit('save-and-add-another', _('Speichern und weiteres Foto hinzufügen'))) |             submits = Div(Submit('submit', _('Speichern')), | ||||||
|             self.helper.add_input(Submit('submit', _('Speichern'))) |                           Submit('save-and-add-another', _('Speichern und weiteres Foto hinzufügen')), | ||||||
|  |                           css_class="container-edit-buttons") | ||||||
|         else: |         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: |     class Meta: | ||||||
|         model = Image |         model = Image | ||||||
| @@ -125,53 +98,76 @@ class ImageForm(forms.ModelForm): | |||||||
|  |  | ||||||
|  |  | ||||||
| class ReportAdoptionNoticeForm(forms.ModelForm): | class ReportAdoptionNoticeForm(forms.ModelForm): | ||||||
|  |     template_name = "fellchensammlung/forms/form_snippets.html" | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|         model = ReportAdoptionNotice |         model = ReportAdoptionNotice | ||||||
|         fields = ('reported_broken_rules', 'user_comment') |         fields = ('reported_broken_rules', 'user_comment') | ||||||
|  |  | ||||||
|  |  | ||||||
| class ReportCommentForm(forms.ModelForm): | class ReportCommentForm(forms.ModelForm): | ||||||
|  |     template_name = "fellchensammlung/forms/form_snippets.html" | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|         model = ReportComment |         model = ReportComment | ||||||
|         fields = ('reported_broken_rules', 'user_comment') |         fields = ('reported_broken_rules', 'user_comment') | ||||||
|  |  | ||||||
|  |  | ||||||
| class CommentForm(forms.ModelForm): | 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: |     class Meta: | ||||||
|         model = Comment |         model = Comment | ||||||
|         fields = ('text',) |         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 ModerationActionForm(forms.ModelForm): | ||||||
|     class Meta: |     class Meta: | ||||||
|         model = ModerationAction |         model = ModerationAction | ||||||
|         fields = ('action', 'public_comment', 'private_comment') |         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 CustomRegistrationForm(RegistrationForm): | ||||||
|     class Meta(RegistrationForm.Meta): |     class Meta(RegistrationForm.Meta): | ||||||
|         model = User |         model = User | ||||||
|  |  | ||||||
|     def __init__(self, *args, **kwargs): |     template_name = "fellchensammlung/forms/form_snippets.html" | ||||||
|         super().__init__(*args, **kwargs) |  | ||||||
|         self.helper = FormHelper() |  | ||||||
|         self.helper.form_id = 'form-registration' |  | ||||||
|         self.helper.form_class = 'card' |  | ||||||
|  |  | ||||||
|         self.helper.add_input(Submit('submit', _('Registrieren'), css_class="btn")) |     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 _get_distances(): |  | ||||||
|     return {i: i for i in [10, 20, 50, 100, 200, 500]} |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class AdoptionNoticeSearchForm(forms.Form): | class AdoptionNoticeSearchForm(forms.Form): | ||||||
|     location = forms.CharField(max_length=20, label=_("Stadt")) |     template_name = "fellchensammlung/forms/form_snippets.html" | ||||||
|     max_distance = forms.ChoiceField(choices=_get_distances, label=_("Max. Distanz")) |  | ||||||
|  |     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_lazy as _ | ||||||
| from django.utils.translation import gettext |  | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from django.core import mail | from django.core import mail | ||||||
| from django.db.models import Q, Min | from fellchensammlung.models import User, Notification, TrustLevel, NotificationTypeChoices | ||||||
| from fellchensammlung.models import User | from fellchensammlung.tools.model_helpers import ndm | ||||||
| from notfellchen.settings import host |  | ||||||
|  |  | ||||||
| NEWLINE = "\r\n" |  | ||||||
|  |  | ||||||
| def mail_admins_new_report(report): | def notify_mods_new_report(report, notification_type): | ||||||
|     subject = _("Neue Meldung") |     """ | ||||||
|     for moderator in User.objects.filter(trust_level__gt=User.TRUST_LEVEL[User.MODERATOR]): |     Sends an e-mail to all users that should handle the report. | ||||||
|         greeting = _("Moin,") + "{NEWLINE}" |     """ | ||||||
|         new_report_text = _("es wurde ein Regelverstoß gemeldet.") + "{NEWLINE}" |     for moderator in User.objects.filter(trust_level__gt=TrustLevel.MODERATOR): | ||||||
|         if len(report.reported_broken_rules.all()) > 0: |         if notification_type == NotificationTypeChoices.NEW_REPORT_AN: | ||||||
|             reported_rules_text = (f"Ein Verstoß gegen die folgenden Regeln wurde gemeldet:{NEWLINE}" |             title = _("Vermittlung gemeldet") | ||||||
|                                    f"- {f'{NEWLINE} - '.join([str(r) for r in report.reported_broken_rules.all()])}{NEWLINE}") |         elif notification_type == NotificationTypeChoices.NEW_REPORT_COMMENT: | ||||||
|  |             title = _("Kommentar gemeldet") | ||||||
|         else: |         else: | ||||||
|             reported_rules_text = f"Es wurden keine Regeln angegeben gegen die Verstoßen wurde.{NEWLINE}" |             raise NotImplementedError | ||||||
|         if report.user_comment: |         notification = Notification.objects.create( | ||||||
|             comment_text = f'Kommentar zum Report: "{report.user_comment}"{NEWLINE}' |             notification_type=notification_type, | ||||||
|         else: |             user_to_notify=moderator, | ||||||
|             comment_text = f"Es wurde kein Kommentar hinzugefügt.{NEWLINE}" |             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}" | def send_notification_email(notification_pk): | ||||||
|         body_text = greeting + new_report_text + reported_rules_text + comment_text + link_text |     notification = Notification.objects.get(pk=notification_pk) | ||||||
|         message = mail.EmailMessage(subject, body_text, settings.DEFAULT_FROM_EMAIL, [moderator.email]) |  | ||||||
|         print("Sending email to ", moderator.email) |     subject = f"{notification.title}" | ||||||
|         message.send() |     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 model_bakery import baker | ||||||
|  |  | ||||||
| from fellchensammlung.models import AdoptionNotice, Species, Animal, Image, ModerationAction, User, Rule, \ | from fellchensammlung.models import AdoptionNotice, Species, Animal, Image, ModerationAction, User, Rule, \ | ||||||
|     Report, Comment, ReportAdoptionNotice |     Report, Comment, ReportAdoptionNotice, TrustLevel | ||||||
|  |  | ||||||
|  |  | ||||||
| class Command(BaseCommand): | class Command(BaseCommand): | ||||||
| @@ -101,10 +101,10 @@ class Command(BaseCommand): | |||||||
|  |  | ||||||
|         User.objects.create_user('test', password='foobar') |         User.objects.create_user('test', password='foobar') | ||||||
|         admin1 = User.objects.create_superuser(username="admin", password="admin", email="admin1@example.org", |         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", |         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) |         comment1 = baker.make(Comment, user=admin1, text="This is a comment", adoption_notice=adoption1) | ||||||
|         comment2 = baker.make(Comment, |         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), | ||||||
|  |     ] | ||||||