789 Commits
Author SHA1 Message Date
moanos ef824d6b82 feat: Download templates when acessing page 2026-02-07 21:40:46 +01:00
moanos 153dc8c048 feat: Add overview page for adoption notice social media actions 2026-02-07 21:36:22 +01:00
moanos 00e32e0f7c refactor: Abstract post to fedi component 2026-02-07 21:35:51 +01:00
moanos 75789f4247 feat: Add instagram story photo generator 2026-02-07 20:19:34 +01:00
moanos 96221a1232 fix: also exclude ongoing communication from check statistics 2026-02-07 20:19:05 +01:00
moanos c230020fc8 fix: Translate Allow material usage 2026-01-20 22:32:53 +01:00
moanos e5c822dbb4 feat: exclude orgs that denied usage from check 2026-01-20 22:27:52 +01:00
moanos 9457b19e83 feat: Make date of birt optional, fixes #30 2026-01-20 22:15:16 +01:00
moanos 0e6a9c7c7d fix: adjust a bit of styling in darkmode 2026-01-20 21:17:06 +01:00
moanos c324394949 feat: add species specific url to api 2026-01-20 21:03:08 +01:00
moanos dfa44ea0c6 feat: add script to export shelters with websites for #29 2026-01-20 21:02:40 +01:00
moanos c309ea9ed4 feat: add meerschweinchen 2026-01-06 18:54:36 +01:00
moanos f3f7131912 feat: add rescue org create and change form 2026-01-06 17:15:52 +01:00
moanos 9a3cbffa42 feat: Add basic name based search for rescue orgs 2025-11-30 09:41:33 +01:00
moanos f2bad5f171 feat: buttons span whole footer 2025-11-30 08:37:38 +01:00
moanos 515d9453d1 fix: use correct db value for migration 2025-11-29 13:17:42 +01:00
moanos d7797ab02e feat: Add nicer display for location and adoption notice 2025-11-29 11:53:44 +01:00
moanos ad3511c086 feat: Flatten status and use django form 2025-11-29 11:27:30 +01:00
moanos f9f35f9104 feat: Add exclude shelters with different purpose 2025-11-26 23:41:03 +01:00
moanos c4da3318c2 feat: Add history tracking 2025-11-16 18:49:01 +01:00
moanos c529153373 feat: Add mod notes and user activation and deavtivation 2025-11-16 18:07:13 +01:00
moanos 8d0e4c62f7 feat: Hide user action buttons 2025-11-16 17:23:20 +01:00
moanos 4ab546bd8e feat: add cover for images and adoption process if inactive 2025-11-16 16:17:19 +01:00
moanos cab9010aff feat: make status of disabled ANs more clear 2025-11-16 13:40:38 +01:00
moanos 97df7e3ed6 fix: remove caching 2025-11-12 20:30:16 +01:00
moanos e64cc4bd5f Merge branch 'develop'
# Conflicts:
#	src/locale/en/LC_MESSAGES/django.po
2025-11-09 17:55:11 +01:00
moanos a498971d66 feat: create directory in one go, don't use cache dir and use more standard entrypoint.sh 2025-11-09 17:36:48 +01:00
moanos 527ab07b6f trans: fis various 2025-11-08 00:33:35 +01:00
moanos e2e236d650 feat: add button to download all logs 2025-11-08 00:00:54 +01:00
moanos c4100a9ade trans: fix various 2025-11-07 18:17:53 +01:00
moanos 9511b46af0 feat: deactivate cache in dev config 2025-11-07 18:13:30 +01:00
moanos 5b906a7708 feat: add clean method 2025-11-07 18:12:53 +01:00
moanos d68e836b57 trans: add various english translations 2025-11-07 18:12:37 +01:00
moanos fe77f1da8d trans: add many translations 2025-11-07 17:08:13 +01:00
moanos 78b71690c0 feat: Allow toggling ongoing communication status 2025-11-07 14:02:22 +01:00
moanos 3b9ee95abc feat: Add log export functionality 2025-11-07 14:01:51 +01:00
moanos b4e50364de fix: dependency 2025-11-03 22:22:22 +01:00
moanos b014b3b227 fix: quotes 2025-11-03 22:22:12 +01:00
moanos 99bfe460ee fix: remove debug statement 2025-11-03 18:24:45 +01:00
moanos d4c7caa42d fix: allow empty internal IP 2025-11-03 18:24:20 +01:00
moanos 32c8fc88cf feat: add debug command that prints settings 2025-11-03 18:22:11 +01:00
moanos 151ce0d88e fix: massively reduce number of db queries by caching num_per_sex #27
ci/woodpecker/push/woodpecker Pipeline was successful
2025-11-03 17:00:16 +01:00
moanos e07e633651 style: explicitly return none 2025-11-03 16:53:59 +01:00
moanos dd3b1fde9d feat: Add logs for checking rescue orgs and remove deprecated exclusion 2025-11-03 16:15:11 +01:00
moanos 2ffc9b4ba1 feat: Ad django debug toolbar 2025-11-03 16:14:51 +01:00
moanos 22eebd4586 feat: Add simple profiler capability 2025-11-03 16:14:05 +01:00
moanos e589a048d3 feat: Make logs in Admin more usable 2025-11-03 16:11:48 +01:00
moanos 392eb5a7a8 feat: Use unified explanation for reason for signup 2025-11-02 08:15:11 +01:00
moanos 44fa4d4880 feat: Remove requirement to retype password 2025-11-02 08:14:41 +01:00
moanos 9b97cc4cb1 fix: Ensure javascript for login is loaded 2025-11-02 08:14:03 +01:00
moanos 656a24ef02 feat: Make settings configurable
ci/woodpecker/push/woodpecker Pipeline was successful
2025-10-21 03:37:36 +02:00
moanos 74643db087 feat: Add nicer display of passkeys based on panels 2025-10-21 03:27:08 +02:00
moanos 3a6fd3cee1 feat: Add nicer badge 2025-10-21 03:24:19 +02:00
moanos 29e9d1bd8c feat: Don't make input for radio button 2025-10-21 03:24:07 +02:00
moanos 3c5ca9ae00 feat: Fix display of 2fa options 2025-10-21 02:12:34 +02:00
moanos 3d1ad6112d feat: Add link to 2fa options 2025-10-21 02:12:17 +02:00
moanos b843e67e9b feat: put buttons in group 2025-10-21 01:47:17 +02:00
moanos 4cab71e8fb feat: Style allauth templates 2025-10-21 01:28:31 +02:00
moanos 969339a95f feat: Use allauth and add passkey support 2025-10-21 00:40:10 +02:00
moanos e06efa1539 feat: limit to 10 2025-10-21 14:47:13 +02:00
moanos 2fb6d2782f fix: Align button description with function 2025-10-20 21:56:04 +02:00
moanos f69eccd0e4 feat: add page for updating the exclusion reason where it's not set yet 2025-10-20 18:33:15 +02:00
moanos e20e6d4b1d fix: typo 2025-10-20 18:31:10 +02:00
moanos 0352a60e28 feat: Add reason why rescue org was excluded from check
ci/woodpecker/push/woodpecker Pipeline was successful
2025-10-20 10:54:23 +02:00
moanos abeb14601a docs: Add general explenation 2025-10-20 10:12:40 +02:00
moanos f52225495d docs: Add number of rescueorgs in table 2025-10-20 10:09:58 +02:00
moanos 797b2c15f7 docs: Add adoption notice lifecycle
ci/woodpecker/push/woodpecker Pipeline was successful
2025-10-19 22:53:29 +02:00
moanos e81618500b docs: Add getting started
ci/woodpecker/push/woodpecker Pipeline was successful
2025-10-19 21:02:51 +02:00
moanos f7a5da306c docs: Add details to notifications 2025-10-19 21:02:33 +02:00
moanos 92a9b5c6c9 fix: typo 2025-10-19 17:46:47 +02:00
moanos 964aeb97a7 docs: documentation on checking rescue orgs
ci/woodpecker/manual/woodpecker Pipeline was successful
2025-10-19 09:41:19 +02:00
moanos 474e9eb0f8 feat: Add extension to sphinx that includes drawio diagrams 2025-10-19 09:40:51 +02:00
moanos 7acc2c6eec feat: Document shortcuts in org-check und documentation 2025-10-19 08:35:19 +02:00
moanos 5a02837d7f feat: Fix age display and test 2025-10-18 18:45:17 +02:00
moanos 7ff0a9b489 feat: Add stats 2025-10-03 18:37:54 +02:00
moanos 9af4b58a4f feat: Add stats 2025-10-03 09:39:48 +02:00
moanos 7a20890f17 feat: Add organization to form 2025-10-01 06:13:01 +02:00
moanos f6c9e532f8 feat: Use status-specific description 2025-10-01 05:58:30 +02:00
moanos f1698c4fd3 fix: use correct new status 2025-10-01 05:54:43 +02:00
moanos cb82aeffde fix: use correct new status 2025-10-01 05:33:49 +02:00
moanos e9c1ef2604 feat: typo 2025-09-29 17:34:46 +02:00
moanos d8f0f2b3be feat: Use status description to more accuratly describe status 2025-09-29 17:34:38 +02:00
moanos 65f065f5ce feat: Move unchecked to awaiting action 2025-09-29 17:34:13 +02:00
moanos 5cba64e500 feat: allow links to break anywhere 2025-09-29 17:32:05 +02:00
moanos 064784a222 feat: Use contact data of parent org if org doesn't have specified 2025-09-27 15:35:39 +02:00
moanos b890ef3563 fix: Use block only when description exists 2025-09-27 15:34:32 +02:00
moanos 962f2ae86c feat: Add contact site 2025-09-17 20:02:20 +02:00
moanos c71a1940dd feat: Add url of adoption notice to API 2025-09-17 20:02:13 +02:00
moanos 8b2913a8be feat: Add Image width and height to API 2025-09-16 16:38:29 +02:00
moanos 111ffc2b2e feat: Add Image alt text to API 2025-09-13 16:53:13 +02:00
moanos 600aa918ef feat: Add Image URL to API 2025-09-13 16:49:00 +02:00
moanos 68e13ed176 feat: Add API to get adoption notices of organization 2025-09-13 16:05:49 +02:00
moanos 0fa4330f2c feat: Add more clear when no Adoption notice is found 2025-09-10 23:29:56 +02:00
moanos c9289b1e8c feat: Add padding to iframe content 2025-09-10 23:29:30 +02:00
moanos bb3136bfc7 feat: Allow setting a background color for embedding 2025-09-10 22:54:24 +02:00
moanos b708e9ecaf feat: Get all adoption notices in hierarchy 2025-09-10 20:18:10 +02:00
moanos f3619b2881 fix: Exclude embeddable from xframe options 2025-09-10 19:28:36 +02:00
moanos 7572c92da5 feat: Add expand option 2025-09-10 19:28:19 +02:00
moanos 5a2b11b44e feat: Add basic embeddable view for ans of rescue orgs 2025-09-10 17:44:33 +02:00
moanos df15ea100b feat: Add stub to translate ANs to sharepics 2025-09-10 13:17:50 +02:00
moanos 3da6e90f73 feat: Show short description for ANs with one picture 2025-09-07 20:36:41 +02:00
moanos f784ab0c78 feat: Restyle the image upload 2025-09-06 16:06:12 +02:00
moanos ebaa477cff feat: Use slug to get specialization 2025-09-06 15:35:17 +02:00
moanos b4be21bf45 feat: Add slug to species 2025-09-06 15:14:04 +02:00
moanos 0df36df9d8 fix: has search criteria had twisted logic 2025-09-06 14:08:30 +02:00
moanos a5754b2633 feat: Optimize search for larger number of ANs 2025-09-06 13:35:19 +02:00
moanos 7c6e01a436 feat: Add various verbose names and helptexts 2025-09-06 13:12:06 +02:00
moanos ad90429ec7 feat: Add local db backups to gitignore 2025-09-05 16:19:13 +02:00
moanos 0e36237890 feat: Add adoption notice template for contacting a person directly 2025-09-05 16:07:21 +02:00
moanos 3261f5a90a fix: Allow to actually close AN 2025-09-03 06:43:12 +02:00
moanos 1551c1bdf2 fix: correctly filter for adoption notices 2025-09-03 06:28:35 +02:00
moanos 996bd7af67 feat: Re-style comment field to be inside comment box 2025-09-03 06:28:13 +02:00
moanos 21bd34c94d feat: Add divider 2025-09-03 06:27:48 +02:00
moanos fb581c940b fix: fix action menu hidden behind animal 2025-09-01 21:57:59 +02:00
moanos b428f46213 fix: unify 2025-08-31 23:21:56 +02:00
moanos 38fe55dd86 fix: Only post to fedi if there is a adoption notice to post 2025-08-31 22:05:26 +02:00
moanos 0da6c425fd feat: Add link to oxitraffic 2025-08-31 22:04:12 +02:00
moanos 81962ab9e7 fix: Make sure tasks are only executed once per hour 2025-08-31 20:41:10 +02:00
moanos 48dd0a6a19 fix: Add a block 2025-08-31 20:34:48 +02:00
moanos 661827a957 chore: Bump 1.2.1 2025-08-31 18:05:29 +02:00
moanos 242de5f749 fix: filter for active adoption notices 2025-08-31 18:05:02 +02:00
moanos bd7f940987 chore: Bump version to 1.2.0 2025-08-31 17:07:03 +02:00
moanos 0634671c84 feat: Style further information more clearly 2025-08-31 17:06:01 +02:00
moanos 1fb5be0cf8 feat: Add description of adoption process 2025-08-31 17:04:08 +02:00
moanos 3f9e4265e5 refactor: move descriptions to dedicated mapping 2025-08-31 16:18:49 +02:00
moanos de21b8b5e5 refactor: remove unused imports 2025-08-31 10:54:48 +02:00
moanos fd481fef2e feat: Fully replace the Adoption Notice Status model with the field 2025-08-31 10:52:48 +02:00
moanos 70f077e393 feat: Add general field-based status and migrate data 2025-08-31 00:28:44 +02:00
moanos 1c7d943a21 feat: Add image preview for uploading new images 2025-08-29 08:47:34 +02:00
moanos 41873ebfe5 fix: Send actually new user notification 2025-08-12 17:17:35 +02:00
moanos fc2dbde064 feat: make 20km default 2025-08-12 06:28:03 +02:00
moanos a372be4af2 fix: don't use search when checking specialized rescue orgs 2025-08-12 06:16:27 +02:00
moanos 5d333b28ab feat: Fix pagination when searching 2025-08-12 06:12:27 +02:00
moanos 84ad047c01 feat: Add search for rescue orgs 2025-08-12 00:06:42 +02:00
moanos c93b2631cb feat: Add shortcut to open rescue org website 2025-08-11 22:16:26 +02:00
moanos 15dd06a91f feat: Add shortcut to mark rescue org as checked 2025-08-11 21:51:30 +02:00
moanos 30ff26c7ef feat: Divide adoption notices of org by active and inactive 2025-08-11 12:58:02 +02:00
moanos 1434e7502a fix: Limit upload of fediverse images to 6 2025-08-11 12:43:15 +02:00
moanos 93b21fb7d0 fix: Only try to access trust level when authenticated 2025-08-10 18:32:15 +02:00
moanos e5c82f392c feat(test): Add test for map and metrics 2025-08-10 18:31:53 +02:00
moanos 0626964461 feat(test): Add test for token showing 2025-08-10 18:11:10 +02:00
moanos 23a724e390 fix: Ensure users of higher trust level are also allowed 2025-08-10 17:51:25 +02:00
moanos 2a9c7cf854 feat(test): Add basic tests for user views 2025-08-10 17:51:05 +02:00
moanos 335630e16d feat(test): Add test for search by location 2025-08-10 17:50:17 +02:00
moanos 6051f7c294 feat(test): Exclude from coverage check 2025-08-10 10:17:59 +02:00
moanos c1ea6cd211 feat(test): Add AN form to basic check 2025-08-10 08:44:28 +02:00
moanos 6c43b46007 refactor: break out search test into own file 2025-08-10 08:43:49 +02:00
moanos dc9e68c4b9 refactor: remoive print 2025-08-10 08:22:57 +02:00
moanos 4b03f99971 feat(test): Add rss feed test 2025-08-09 16:46:07 +02:00
moanos 426f4b3d8b fix: Make sure e-mail is sent when comment is reported 2025-08-09 12:30:42 +02:00
moanos 3604233507 fix (test): Rules are now shown on terms of service page 2025-08-09 12:30:22 +02:00
moanos 8c5099f14a fix (test): Notification framework changed 2025-08-09 12:16:13 +02:00
moanos d5bc348453 feat: allow marking read with very ugly double delete class 2025-08-03 10:40:42 +02:00
moanos bce98cb439 trans: Translate models 2025-08-03 10:29:47 +02:00
moanos 1ed3d27533 feat: add option to sync to twenty 2025-08-03 10:00:42 +02:00
moanos 39a098af8e feat: Add option to mask e-mails and phone numbers
This is a prerequisite to do tests on DEv and UAT systems
2025-08-01 20:28:51 +02:00
moanos 62491b84c1 feat(seo): Add basic description 2025-08-01 19:32:02 +02:00
moanos 81f7f5bb5d fix: Use correct heading hierarchy 2025-08-01 19:31:37 +02:00
moanos 8ce4122160 feat: raise 404 when AN not found 2025-07-30 08:01:34 +02:00
moanos 370ad2ce66 feat: add warning when an is waiting for review 2025-07-30 06:57:31 +02:00
moanos f25c425d85 feat: add warning when someone is interested 2025-07-30 06:51:29 +02:00
moanos d921623f31 fix: Make sure content class is used when rendering markdown 2025-07-25 22:38:49 +02:00
moanos 2589f1c703 fix: Make sure rescue orgs with ans only in hierarch show correctly
ci/woodpecker/push/woodpecker Pipeline was successful
2025-07-22 13:25:47 +02:00
moanos 0edb9094c4 feat: Show parent org 2025-07-21 17:11:13 +02:00
moanos bc8feba701 feat: Show all adoption notices of rescue org and children 2025-07-20 16:58:18 +02:00
moanos f37d74a7d1 feat: Add child orgs to org detail page 2025-07-20 16:29:56 +02:00
moanos fa8612ad1a fix: ensure location is displayed 2025-07-20 16:29:27 +02:00
moanos 1d8a054b06 feat: Add number of animals per sex to metrics 2025-07-20 16:09:59 +02:00
moanos 5898fbf86d feat: Add number of animals per sex to metrics 2025-07-20 15:43:59 +02:00
moanos cd1cdd2e0b feat: Add link to admin interface 2025-07-20 15:32:13 +02:00
moanos c0f920544b feat: Add automatic post in the evening 2025-07-20 13:49:24 +02:00
moanos 36c90531a8 feat: Add option to switch between normal and dq 2025-07-20 13:42:56 +02:00
moanos 7f7c5a3b04 fix: post all available pictures 2025-07-20 08:50:00 +02:00
moanos c084e56ad8 chore: Bump version to 1.1.0 2025-07-20 07:59:51 +02:00
moanos 84acc3c76e feat: format posts in markdown 2025-07-20 07:58:36 +02:00
moanos e1f0014898 feat: Add "Post to Fediverse" 2025-07-20 07:07:33 +02:00
moanos 05b3a470f3 feat: Add warning about deactivated ANs 2025-07-19 09:29:15 +02:00
moanos ebe060646a trans: Add a few translations 2025-07-17 15:57:22 +02:00
moanos bb412be8d3 feat: Add view for specialized rescues 2025-07-14 07:31:08 +02:00
moanos e3c48eac24 feat: fail more gracefully 2025-07-14 07:16:17 +02:00
moanos da89cdceda feat: use simpler m2m relationship for specialization 2025-07-14 07:15:44 +02:00
moanos 5a6c2c99e5 feat: add important locations and buying to sitemap and fix 2025-07-14 06:33:12 +02:00
moanos 9f53836ce8 feat: add page dedicated to buying animals 2025-07-14 06:18:56 +02:00
moanos 5d53d1a1dc feat: add a default order for rescue orgs (very useful when adding ANs to it) 2025-07-13 13:01:58 +02:00
moanos e00dda1dc2 feat: add option to mark a rescue org to be in active communication
That enables to filter them out from a check without forgetting there are to-dos. Most often this will be used when you want to call a rescue org but they can currently not be reached
2025-07-13 12:58:14 +02:00
moanos a93e0c819f fix: use correct user 2025-07-13 12:08:42 +02:00
moanos c87733b37a feat: Open shelters in new tab 2025-07-13 11:02:00 +02:00
moanos 9aa964bf05 feat: style external site warning 2025-07-13 10:30:51 +02:00
moanos dcb1d3ec15 feat: Add functionality to deactivate AN with reason 2025-07-13 10:06:13 +02:00
moanos 5d9b8f3213 feat: Re-add functionality to set AN as checked 2025-07-13 09:12:24 +02:00
moanos d12989d195 feat: Add display of when last checked 2025-07-13 09:11:51 +02:00
moanos a9f384b50e feat: add subscribe functionality again 2025-07-13 01:08:43 +02:00
moanos afedf2d0bd feat: add button to mark all notifications as read and fix action 2025-07-13 00:31:21 +02:00
moanos a4b8486bd4 feat: make use of new notification mapping 2025-07-13 00:00:30 +02:00
moanos d8bcb8ece6 refactor: remove redundant imports 2025-07-12 17:45:21 +02:00
moanos b01ac219a3 feat: Add notification partials including new mapping system for templates 2025-07-12 17:43:40 +02:00
moanos 42320866c4 feat: Show nicer time of creating the notification 2025-07-12 16:46:56 +02:00
moanos e2e6c14d57 feat: Show newest notification first 2025-07-12 16:46:17 +02:00
moanos 4761c38cd2 feat: Move cards to bulma notifications 2025-07-12 16:31:15 +02:00
moanos e2bef3efe2 feat: Add dedicated notification page 2025-07-12 14:01:40 +02:00
moanos bbfd4c3800 feat: re-add notification badge 2025-07-12 13:34:18 +02:00
moanos b671d8fbb4 fix: fix filters 2025-07-12 13:34:02 +02:00
moanos 1ea04e98e8 fix: fucking wired bug where the url is not displayed in plaintext
that how an e-mail looked before:
Moin,

es wurde ein neuer Useraccount erstellt.

[admin] sgsfg (2025-07-12 09:44:27.251212+00:00)
Related:

http://localhost:8000/user/9/
User anzeigen:

---
2025-07-12 11:46:47 +02:00
moanos c1a7d6790b feat: Reorder notification fields for nicer display in admin 2025-07-12 11:25:08 +02:00
moanos f519f78922 feat: Add unsubscribe link 2025-07-12 11:24:44 +02:00
moanos 551b5ed6be feat: add plaintext versions of the e-mail 2025-07-12 09:17:58 +02:00
moanos 20cbb0397a Merge branch 'notification_rework' into develop
ci/woodpecker/push/woodpecker Pipeline was successful
2025-07-11 22:12:57 +02:00
moanos 26f999c4cf fix: url 2025-07-11 18:37:43 +02:00
moanos 9858dfc1bd fix: search notification 2025-07-11 18:34:30 +02:00
moanos 2d1df879dc fix: use complete URL 2025-07-11 18:26:41 +02:00
moanos 2f12dc6a5e fix: use complete URL 2025-07-11 18:19:28 +02:00
moanos 4b3286f12d fix: revert allowing an empty user to notify 2025-07-11 17:51:39 +02:00
moanos d53c5707b8 feat: remove notification bell that might not get displayed 2025-07-11 17:34:55 +02:00
moanos ba64661217 refactor: remove prints 2025-07-11 17:34:39 +02:00
moanos 554de17e9e fix: Add missing migration 2025-07-11 17:34:18 +02:00
moanos 5bc4b538e6 refactor: formatting 2025-07-11 17:33:58 +02:00
moanos 8a691d59e7 feat: add base url in order to properly do URLs in e-mails 2025-07-11 16:53:01 +02:00
moanos e99798ba5c feat: Completely rework notification framework
This change is needed as
a) there are many different types of notifications. A notification about a new comment does not only relate the comment but also adoption notice and a user that made the comment. Therefore, I introduce a new general notification that has optional links to other objects.
b) this change allows to define a different representation of e-mails vs. display on the website. The new notification type is in charge of representation from now on. Title and text will be deprecated and eventually removed
2025-07-11 11:39:55 +02:00
moanos caa962700b feat: add link to admin site 2025-07-10 20:51:49 +02:00
moanos 648466aa70 feat: Move report e-mail to html 2025-07-07 21:45:49 +02:00
moanos 25f84bf2ad try: remove html password reset 2025-07-07 21:10:03 +02:00
moanos ac2147095a try: add html password reset 2025-07-07 21:09:36 +02:00
moanos 4856c720b1 feat: Make account activation use html e-mail 2025-07-07 21:01:21 +02:00
moanos ba8ff743f2 feat: Add option to send test-html e-mail 2025-07-07 20:23:00 +02:00
moanos 1e827af2dd feat: Ensure Default from is always set 2025-07-07 19:57:53 +02:00
moanos 7d107abc6a fix: var name 2025-07-07 19:57:16 +02:00
moanos 3bb1cd29cd fix: spacing 2025-07-07 19:18:04 +02:00
moanos 0388367c7a fix: card styling 2025-07-07 18:39:27 +02:00
moanos a8843dfc8f fix: ensure heading styling 2025-07-07 17:56:30 +02:00
moanos 9d1264b6e6 fix: ensure heading styling 2025-07-07 17:56:02 +02:00
moanos 8df04519b9 fix: ensure heading styling 2025-07-07 17:54:04 +02:00
moanos 112a731cd6 fix: ensure heading styling 2025-07-07 17:51:45 +02:00
moanos 8b4ff83921 fix: ensure heading styling 2025-07-07 17:51:00 +02:00
moanos 2249b615f4 fix: ensure heading styling 2025-07-07 17:50:06 +02:00
moanos fbfc800453 feat: Add submit functionality and styling 2025-07-07 17:41:52 +02:00
moanos 752aaf9b89 fix: format search overview result a bit 2025-07-07 17:24:09 +02:00
moanos f1a0d5f475 fix: ensure heading is styled 2025-07-07 17:17:40 +02:00
moanos fb5f38b3e6 fix: make sure main content does not overwrite content class of bulma 2025-07-07 17:13:50 +02:00
moanos ded5299387 fix: stop overwriting of page number (is string when coming in) 2025-07-07 17:13:26 +02:00
moanos 090548905f feat: Add annotations to drf spectacular 2025-07-07 16:59:13 +02:00
moanos 12a89b6927 feat: ensure page number is set 2025-07-07 16:42:20 +02:00
moanos fc4c348ff9 feat: translate strings 2025-07-07 16:30:08 +02:00
moanos 1cd133e335 feat: improve style of update buttons 2025-07-03 21:27:31 +02:00
moanos b5dc6ca97d fix: remove unsafe link generation 2025-07-03 21:18:54 +02:00
moanos f7b98c9dfe fix: typo 2025-07-03 20:59:53 +02:00
moanos 8e9f4e2b2e feat: reduce number of rescue orgs per page 2025-07-03 19:53:26 +02:00
moanos b3faa06c4c feat: simplify pagination by using get_elided_page_range 2025-07-03 19:51:31 +02:00
moanos 165aeb6dcc feat: basic pagination 2025-07-03 19:40:52 +02:00
moanos 24bae28cec feat: use csrf token 2025-07-03 18:38:27 +02:00
moanos 65182d4c2f feat: Add much improved add adoption notice form
TODO: Solve token issue
2025-07-03 17:45:26 +02:00
moanos 1d879993c9 feat: Add organization to adoption notice detail page 2025-07-03 16:39:17 +02:00
moanos f3fbf3ba1d feat: Add SpeciesSpecialization to rescue org form 2025-07-03 11:45:25 +02:00
moanos 5a24f32327 fix: String representation 2025-07-03 11:45:07 +02:00
moanos d72bd22f6d feat: Add species specialization and parent option to Rescue Orgs 2025-07-03 11:29:11 +02:00
moanos 0eb94038f6 docs: add getting started section 2025-07-03 11:07:35 +02:00
moanos 3a69397c0a docs: move docs dependencies to optional 2025-07-03 11:07:18 +02:00
moanos d49cb5a783 docs: Minor api docs adjustments 2025-07-03 10:35:25 +02:00
moanos c13805dd75 fix: remove unecessary print 2025-06-29 18:08:56 +02:00
moanos bcf8a0e6e4 fix: add host to image - will break if cdn is used 2025-06-29 18:06:58 +02:00
moanos 81f9398da4 fix: add image us property for description 2025-06-29 11:06:08 +02:00
moanos 32c90aecd3 fix: canonical url 2025-06-29 10:49:52 +02:00
moanos 01d6c1e0f6 feat: add metatags for search 2025-06-29 10:32:53 +02:00
moanos 607a442e22 feat: add various metatags 2025-06-29 08:56:46 +02:00
moanos b2a79b3547 fix: tag 2025-06-29 08:39:02 +02:00
moanos 63c692d46b feat: start adding canonical urls 2025-06-29 08:26:26 +02:00
moanos 3d2ef9e735 refactor: formatting 2025-06-29 08:24:28 +02:00
moanos aee8b0c1e8 feat: add og tags to adoption notice detail 2025-06-28 20:36:29 +02:00
moanos d5e28ba3d9 fix: load tag 2025-06-28 20:19:56 +02:00
moanos 1edaf4df14 feat: add og tags 2025-06-28 20:02:04 +02:00
moanos b6d31e3c3b feat: add base for js-ification of add adoption form 2025-06-27 15:36:57 +02:00
moanos ed7b55c090 feat: move print to logging 2025-06-27 15:27:07 +02:00
moanos e66e9ad888 fix(accessibility): Hide report flag aria 2025-06-24 22:10:03 +02:00
moanos 4ad8b30e04 fix(accessibility): Adjust link color of footer idem in darmode 2025-06-24 22:09:45 +02:00
moanos ae2ac5c462 fix(accessibility): Make darkmode have sufficient contrast 2025-06-24 13:47:24 +02:00
moanos 2330542a85 fix(accessibility): don't use tabindex > 0
See https://developer.mozilla.org/en-US/docs/Web/Accessibility/Guides/Understanding_WCAG/Keyboard?utm_source=devtools&utm_medium=a11y-panel-checks-keyboard#avoid_using_tabindex_attribute_greater_than_zero
2025-06-24 13:47:15 +02:00
moanos 7363e1ab30 fix(accessibility): Make main menu focusable 2025-06-24 13:45:14 +02:00
moanos d2131b2c91 feat: don't count excluded rescue orgs in statistics 2025-06-23 22:41:19 +02:00
moanos 78866c86cd fix: catch error if there are no rescue orgs 2025-06-23 22:34:44 +02:00
moanos f5dbccb9c4 feat: Make build compressing the css 2025-06-23 22:30:24 +02:00
moanos 8ab38cc71b refactor: Move all css to main 2025-06-23 20:39:41 +02:00
moanos 7dfcbfe38f feat: Add catogories, FN, website and GEO 2025-06-23 19:21:33 +02:00
moanos cd8471036c feat: Add initial support for contacts 2025-06-23 18:38:15 +02:00
moanos c3ec477a6e feat: Make spacing of FA icons uniform 2025-06-23 17:57:56 +02:00
moanos 5a6294adf6 fix: Add asterix for mandatory fields 2025-06-23 17:50:15 +02:00
moanos 1ba44cdd67 Add placeholder for contact export 2025-06-23 17:23:12 +02:00
moanos dfeb88f980 feat: ensure spacing 2025-06-23 17:19:29 +02:00
moanos 44a724809f feat: enforce maximum width and center 2025-06-23 17:16:02 +02:00
moanos 1acd4be953 fix: Unify display of sex badges 2025-06-23 17:15:48 +02:00
moanos 930a5383c4 Merge branch 'admin_link' into develop 2025-06-23 12:30:09 +02:00
moanos eda6da7f12 fix: Fix link to admin interface
see https://forum.djangoproject.com/t/reverse-admin-urls/41535/2
2025-06-23 12:24:53 +02:00
moanos 448fc395d8 fix: percentage 2025-06-23 12:19:21 +02:00
moanos 7e8b665c7c feat: add simple stats 2025-06-22 22:00:39 +02:00
moanos 7be61bc9b8 feat: try reverse 2025-06-22 21:27:52 +02:00
moanos 6828af76dc feat: Add display of permission 2025-06-22 17:58:38 +02:00
moanos fe92d762be feat: Add form for internal comment in dq view 2025-06-22 17:35:36 +02:00
moanos b91a17e950 feat: Add link to admin interface 2025-06-22 14:18:22 +02:00
moanos aabc549bcf feat: improve display performance of rescue orgs by limiting 2025-06-22 09:06:24 +02:00
moanos 4bb4d0386b release: 1.0.1 2025-06-21 08:18:19 +02:00
moanos a74af5e4d3 refactor: don't rebuild logic 2025-06-21 08:07:34 +02:00
moanos a939d53286 refactor: Remove deprecated str_hr 2025-06-21 08:04:52 +02:00
moanos 35c6aae552 feat: style buttons 2025-06-21 07:49:50 +02:00
moanos 83bd6cf7e6 fix: ensure links as buttons have no underline 2025-06-21 07:46:21 +02:00
moanos 17a0cfbde0 feat: allow filtering for rescues orgs without external source 2025-06-21 07:42:51 +02:00
moanos dbcad42da0 feat: allow searching for rescue org based on city 2025-06-21 07:42:18 +02:00
moanos 1b48022b63 release: Release v1.0.0 2025-06-20 19:52:30 +02:00
moanos 7b8e3061d5 feat: add styled footer items for cards 2025-06-20 19:52:04 +02:00
moanos 3f27564075 feat: allow to exclude from check 2025-06-20 19:06:41 +02:00
moanos e7c2746eab feat: add data quality view to add species specific urls 2025-06-20 19:00:53 +02:00
moanos 1e243496fb fix: get species urls 2025-06-20 19:00:28 +02:00
moanos 53bc433aaa fix: typo 2025-06-20 18:53:09 +02:00
moanos 6f5e75a1b3 refactor: change comment form to bulma 2025-06-20 18:42:28 +02:00
moanos f83851b694 docs: Add a more detailed description 2025-06-20 09:19:07 +02:00
moanos 3f5a5dceb5 feat: show number of results in search 2025-06-20 09:06:19 +02:00
moanos f9c7dd8c39 feat: decrease size and make levels mobile responsive 2025-06-20 08:50:00 +02:00
moanos f3e437dbd1 Merge branch 'overpass-take-2' into develop
# Conflicts:
#	scripts/upload_animal_shelters.py
2025-06-20 08:08:42 +02:00
moanos 5ee1e61eac fix: ensure that search is actually using search params 2025-06-20 08:05:34 +02:00
moanos abd34ec7cb feat: allow using cached results 2025-06-20 08:05:11 +02:00
moanos b73f6db7b6 feat: dump data in geojson format 2025-06-20 08:04:13 +02:00
moanos 3b9f10dad7 fix: Make sure that location uses center, don't provide fediverse or description if empty and actually create and use location 2025-06-20 08:03:40 +02:00
Patrick Moore 53c0e8b3b8 feat: Merge moan0s changes
These changes may enhance animal shelter data handling with new add_if_available function and improved organization checks
2025-06-19 23:11:58 -04:00
Patrick Moore 7d264fe131 fix: Update get_overpass_result to accept data_file parameter and save response to file 2025-06-19 23:04:31 -04:00
Patrick Moore c968b39657 fix: Refactor Overpass query to use requests and osmtogeojson for animal shelter data retrieval 2025-06-19 23:04:31 -04:00
Patrick Moore ebf116f347 feat: Add Overpass API integration for animal shelter data retrieval 2025-06-19 23:04:25 -04:00
moanos 8227866e7a refactor: rename functions 2025-06-19 23:34:20 +02:00
moanos 6f5e73b533 fix: correct link 2025-06-19 23:34:05 +02:00
moanos a302a36fd4 feat: Allow excluding rescue org from checking 2025-06-19 23:33:48 +02:00
moanos 1307b2ff7b feat: redesign rescue check 2025-06-19 23:22:53 +02:00
moanos d9730e765e feat: add mod tool overview 2025-06-19 23:06:30 +02:00
moanos 8420c698d4 feat: add logout button 2025-06-19 22:56:39 +02:00
moanos 14be917d43 fix: remove unnecessary classes, don't put link in paragraph to not underline 2025-06-19 22:12:16 +02:00
moanos 20da09fb96 fix: show contact data when available 2025-06-19 22:04:32 +02:00
moanos 19de7d3e4c fix: view name 2025-06-19 17:57:01 +02:00
moanos 41d821b86e fix: template path 2025-06-19 17:56:04 +02:00
moanos b758b54233 fix: external site template 2025-06-19 17:55:32 +02:00
moanos c650266c6b trans: typo update trans files 2025-06-19 17:52:51 +02:00
moanos dae9bb0916 fix: typo 2025-06-19 17:51:47 +02:00
moanos 2f2371d8df feat: Rework update queue to use card 2025-06-19 17:50:03 +02:00
moanos baaf4b70ac feat: Rework search subscriptions to use panel 2025-06-19 17:38:05 +02:00
moanos 3b1cd800f6 feat: Improve search subscription display 2025-06-19 17:24:46 +02:00
moanos 0f5f7216ac fix: fix link 2025-06-19 17:22:46 +02:00
moanos c40872379e feat(a11y): underline links for accessibility 2025-06-19 17:18:12 +02:00
moanos 897ac5ceef feat: properly space rules and TOS 2025-06-19 17:17:40 +02:00
moanos eb3dbb3e45 trans: Add general de translation 2025-06-19 17:17:22 +02:00
moanos 9eb6042ba7 trans: Finish en translation 2025-06-19 17:07:01 +02:00
moanos 075833aa25 trans: Improve translation 4 2025-06-19 16:57:02 +02:00
moanos f84d800bff trans: Improve translation 3 2025-06-19 16:52:29 +02:00
moanos 20f814b0ef trans: Improve translation 2 2025-06-19 16:44:01 +02:00
moanos 4376a63e93 trans: Improve translation 1 2025-06-19 16:37:06 +02:00
moanos ee3d316175 docs: Document css build 2025-06-19 14:31:47 +02:00
moanos 452113b4bf feat: Rewrite search with jquery and use proper dropdown 2025-06-19 14:25:01 +02:00
moanos e9b28ea1c1 feat: restructure search to showhelptext nicer 2025-06-19 11:58:45 +02:00
moanos ba07533667 feat: restrict ANs to 4 on index 2025-06-19 11:48:21 +02:00
moanos 1ab5c4885e feat: allow smaller tags 2025-06-19 11:43:53 +02:00
moanos 8c977cf255 refactor: normalize name of detail pages 2025-06-19 08:05:40 +02:00
moanos d4c6014e17 refactor: normalize form template names 2025-06-19 08:05:13 +02:00
moanos 078e5e28cc refactor: remove deprecated template 2025-06-19 08:04:14 +02:00
moanos 7010b4f3d2 refactor: rename lists 2025-06-19 08:03:11 +02:00
moanos 43f38b88ce refactor: rename partials 2025-06-19 08:02:43 +02:00
moanos 7a12a1a4d6 refactor: remove deprecated template 2025-06-19 08:01:43 +02:00
moanos d784f14c4c refactor: remove deprecated template 2025-06-19 08:00:19 +02:00
moanos 339cdf3ea9 fix: don't show age twice 2025-06-19 07:59:44 +02:00
moanos 060be3b486 refactor: remove deprecated templates 2025-06-19 07:59:28 +02:00
moanos 9f93a19d51 refactor: rename templates of main sites 2025-06-19 07:55:43 +02:00
moanos c131c07afe refactor: rename template search 2025-06-19 07:53:10 +02:00
moanos 42dbf5c6f7 feat: Add information on subscribing to search 2025-06-19 06:41:09 +02:00
moanos 2e9039a569 feat: Improve display when no contactdata is there + formatting 2025-06-18 20:05:23 +02:00
moanos 64b48efafb feat: Split up comments and comment form 2025-06-18 20:04:45 +02:00
moanos 37e8dc4bdc feat: add photo and edit option to 2025-06-18 20:04:28 +02:00
moanos 61deb96961 fix: fix rendering of adoption notice edit form 2025-06-18 14:59:35 +02:00
moanos 3a6ce1d38b feat: restructure contact data view 2025-06-18 14:59:12 +02:00
moanos 82fb73ae59 feat: Ensure using local font 2025-06-18 14:58:46 +02:00
moanos 4e71c8704f feat: Ensure using local photoswipe 2025-06-18 14:58:37 +02:00
moanos 0c1edf647b fix:display ANs 2025-06-18 13:00:58 +02:00
moanos 9b38898a8a fix: really ignore files 2025-06-18 13:00:47 +02:00
moanos 25348e45e0 feat: Add slider and styles 2025-06-18 12:59:21 +02:00
moanos 631c2360e6 feat: Don't track compiled css 2025-06-18 12:59:03 +02:00
moanos 6798cf3477 feat: Convert detail user to bulma 2025-06-18 12:55:40 +02:00
moanos cc873d6029 feat: Show link to user profile for logged-in users 2025-06-18 12:55:25 +02:00
moanos 5d147a4fc9 feat: expand link until link symbol 2025-06-18 12:55:05 +02:00
moanos 640862d8ee feat: don't use card, look shitty 2025-06-18 12:54:21 +02:00
moanos 99bb53a7a3 feat: use new style in rss 2025-06-18 12:53:59 +02:00
moanos 4f05dc18b9 feat: use new maintainer label 2025-06-18 12:53:44 +02:00
moanos 2a2df3bf52 feat: Use scss for bulma 2025-06-18 07:25:09 +02:00
moanos c2b15c2175 fix: Make actual form 2025-06-18 07:12:29 +02:00
moanos edc27b899e fix: Only show rescue orgs and animal shelters when relevant 2025-06-17 23:35:11 +02:00
moanos 59d96e36a4 refactor: Make rescue Orgs clustered 2025-06-17 23:07:30 +02:00
moanos 2c976f926c refactor: Remove old get abolute URL 2025-06-17 22:46:50 +02:00
moanos 671c6ec6f5 feat: Add rescue orgs as geojson via API 2025-06-17 22:43:14 +02:00
moanos ef9ac58c0f feat: indicate link 2025-06-17 22:17:16 +02:00
moanos 60e6fdf4e4 refactor: remove old partial for map 2025-06-17 22:15:23 +02:00
moanos 06e6455ba0 feat: adjust sizes 2025-06-17 19:49:00 +02:00
moanos 007eb3b5a9 fix: remove styleguide also from urls 2025-06-17 17:05:32 +02:00
moanos f3333f2da4 refactor: remove header and base 2025-06-17 17:03:42 +02:00
moanos 96b40c5169 refactor: move to bulma 2025-06-17 17:03:26 +02:00
moanos d81408b79c refactor: remove animal detail view 2025-06-17 17:02:42 +02:00
moanos 5ae5e90461 refactor: make detail user use bulma 2025-06-17 17:01:39 +02:00
moanos 2534ef3319 refactor: remove deprecated detail rescue orgs 2025-06-17 17:01:05 +02:00
moanos 0c2e774891 refactor: remove deprecated styleguides 2025-06-17 17:00:30 +02:00
moanos 895bb3c901 refactor: remove deprecated map 2025-06-17 16:59:03 +02:00
moanos fca5445aa7 refactor: remove deprecated startpage 2025-06-17 16:58:38 +02:00
moanos 31d2b85b2f refactor: remove deprecated external site warning 2025-06-17 16:58:17 +02:00
moanos a8b3214c49 refactor: remove depreacted rescue org view 2025-06-17 16:55:38 +02:00
moanos ccdfd388c4 refactor: remove depreacted about 2025-06-17 16:53:49 +02:00
moanos cad6acd125 refactor: move to bulma 2025-06-17 16:53:11 +02:00
moanos 8dc9c1b9e7 refactor: Remove deprecated 2025-06-17 16:52:24 +02:00
moanos cd4de2528f feat: return 403 for unexpected operation 2025-06-17 16:51:29 +02:00
moanos 3ef4b98c1c feat: move activation forms 2025-06-17 16:51:12 +02:00
moanos 349917e887 feat: Only show buttons if you are allowed to use them 2025-06-17 16:45:54 +02:00
moanos 6c200ba076 feat: Add option to add animal on UI 2025-06-17 13:13:55 +02:00
moanos f16aa845d2 feat: Re-add option to add animal 2025-06-17 13:00:17 +02:00
moanos 3bccb1e690 feat: Add option to delete animal 2025-06-17 12:35:35 +02:00
moanos 10ae697e33 fix: name 2025-06-17 07:20:58 +02:00
moanos baf0d2db72 fix: Always use bulma for ANs 2025-06-17 07:20:22 +02:00
moanos b30123a890 fix: displayed these 2025-06-17 07:17:41 +02:00
moanos 44c34d2daa refactor: Remove non-bulma adoption detail 2025-06-17 07:13:03 +02:00
moanos e010fa413b refactor: Remove non-bulma rescue org 2025-06-17 07:07:24 +02:00
moanos e79aca4efa refactor: Move to bulma 2025-06-17 07:05:40 +02:00
moanos 037f6529fd refactor: Remove non-bulma map 2025-06-17 07:05:32 +02:00
moanos 14752d9746 refactor: Remove non-bulma about 2025-06-17 07:02:24 +02:00
moanos a8b2bd4e90 refactor: Rename 2025-06-17 07:01:42 +02:00
moanos 60ae971f14 refactor: Remove non-bulma rescue org view 2025-06-17 07:00:00 +02:00
moanos 1920d72821 refactor: Remove non-bulma add image for ANs 2025-06-17 06:58:16 +02:00
moanos 01aa8baadd refactor: Remove non-bulma add image 2025-06-17 06:55:57 +02:00
moanos f2f526c9de refactor: Remove non-bulma add-adoption 2025-06-17 06:53:06 +02:00
moanos 4d490690e2 refactor: Remove index 2025-06-17 06:49:38 +02:00
moanos d9c7aa8c49 refactor: Remove deprecated search 2025-06-17 06:45:22 +02:00
moanos 040299b90c feat: Activate report option 2025-06-17 05:59:48 +02:00
moanos 0bd321e5ec feat: Convert report detail to bulma 2025-06-17 05:57:43 +02:00
moanos c038370602 feat: Convert report form to Bulma 2025-06-17 05:40:54 +02:00
moanos c08f7fc792 feat: route to bulma after completion 2025-06-16 23:33:22 +02:00
moanos 4dd35c3866 refactor: Simplify Adoption Notice Forms 2025-06-16 23:32:09 +02:00
moanos 8bd041d7ea fix: Make sure further information field is displayed 2025-06-16 23:31:20 +02:00
moanos d450ad42c0 fix: Make sure date is pre-filled
https://stackoverflow.com/questions/58294769/django-forms-dateinput-not-populating-from-instance
2025-06-16 23:17:27 +02:00
moanos d34dcada09 feat: Add support for checkbox and textarea 2025-06-16 23:09:57 +02:00
moanos d8448de419 feat: Add links to edit and remove link to animal detail page 2025-06-16 23:03:51 +02:00
moanos 35ef6676a2 feat: Make proper animal edit form 2025-06-16 23:03:22 +02:00
moanos e132b1c9f6 feat: Add date support and simplify 2025-06-16 23:01:12 +02:00
moanos 5511d8275c feat: Add ideas for actions 2025-06-16 21:00:10 +02:00
moanos accf877375 feat: Show image of AN only when there are some 2025-06-16 20:52:25 +02:00
moanos 9ac362fa58 feat: Increase view height to reliably show AN popup 2025-06-15 20:44:37 +02:00
moanos 9253fde2e5 feat: Move tags to content and only show gallery when there are pictures 2025-06-15 20:43:39 +02:00
moanos 975c962025 feat: Use rat image 2025-06-15 18:37:51 +02:00
moanos ba72b4e59f feat: Get optional photo 2025-06-15 18:01:48 +02:00
moanos 89e001bd17 feat: truncate description 2025-06-15 17:42:02 +02:00
moanos 623ca8bc0a fix: make sure breakpoints are aligned with bulma mobile 2025-06-15 17:41:52 +02:00
moanos 0b483ce630 feat: Feed map with real data 2025-06-15 17:25:55 +02:00
moanos 16998b85d5 feat: Improve clustering look 2025-06-15 15:52:42 +02:00
moanos b55952ac67 feat: fucking bug fixed
Needs a font that versatiles provides such as https://tiles.hyteck.de/assets/glyphs/index.json

Doesn't work with fallback
2025-06-11 17:56:54 +02:00
moanos 30967dac33 feat: use clustering (fucking bug not fixed) 2025-06-10 22:38:46 +02:00
moanos 3166faa7eb feat: Make search specific for cities and center around germany 2025-06-10 21:04:55 +02:00
moanos 9bba81be22 refactor(bulma): Ensure search suggestions are not overshadowed by autocomplete 2025-06-10 21:04:26 +02:00
moanos 18a2d16bf6 refactor(bulma): Add action dropdown, remove card layout 2025-06-10 20:35:24 +02:00
moanos 9265cdaea9 refactor(bulma): move registration/login to end of navbar 2025-05-25 17:16:48 +02:00
moanos fcb9b60656 refactor(bulma): fix close option 2025-05-25 15:27:01 +02:00
moanos 599702f50a refactor(bulma): add announcement template 2025-05-25 15:19:43 +02:00
moanos c8453db69d refactor(bulma): add rescue orgs to footer 2025-05-25 11:37:16 +02:00
moanos 6ad93abe3b refactor(bulma): convert check rescue org 2025-05-25 11:36:47 +02:00
moanos 3c60782ae7 refactor(bulma): move contact details to partial 2025-05-25 11:04:29 +02:00
moanos 736f645bf0 refactor(bulma): convert external site warning 2025-05-25 00:20:53 +02:00
moanos b0887ab731 refactor(bulma): convert instance healthcheck 2025-05-25 00:15:55 +02:00
moanos ada194122d refactor(bulma): convert all account related templates 2025-05-25 00:10:47 +02:00
moanos b3d1ec142b feat: Make all forms to be rendered bycustom renderer 2025-05-25 00:10:12 +02:00
moanos e7a8a163f1 feat: Add helptexts to custom form renderer 2025-05-24 23:40:58 +02:00
moanos c3ef54a267 fix: Re-add location 2025-05-24 19:45:21 +02:00
moanos da3b43a713 feat: Make sure patch doesn't overwrite 2025-05-24 19:41:53 +02:00
moanos 8cfddd7882 refactor: formatting 2025-05-24 17:56:36 +02:00
moanos 80eafbb014 fix: get id correctly 2025-05-24 17:55:28 +02:00
moanos cc2a659767 feat: only fetch the location if none is given 2025-05-24 13:06:23 +02:00
moanos cacfeff3fe feat: support all types of geometry types 2025-05-24 13:05:32 +02:00
moanos 9379728b71 feat: comments 2025-05-24 12:09:12 +02:00
moanos d30d15c0d4 feat: exclude geojson from git 2025-05-24 12:05:30 +02:00
moanos 5343f53661 feat: skip rescues that already exist 2025-05-23 20:03:28 +02:00
moanos 3126b2b962 feat: return 404 when no organizations are found 2025-05-23 20:01:46 +02:00
moanos 43c671018b refactor: remove redundant serializer 2025-05-23 19:51:28 +02:00
moanos 7a37377a09 feat: make location string optional but enforce either location or location string 2025-05-23 19:49:52 +02:00
moanos 19d9dea8b1 feat: add location deduplication 2025-05-23 18:13:23 +02:00
moanos c50e0b18b5 refactor:naming 2025-05-23 16:50:45 +02:00
moanos 4c07c0feb2 feat: Add location as object when uploading shelters 2025-05-22 22:01:58 +02:00
moanos cf15b60bef refactor(bulma): Add support for rescue organizations 2025-05-22 19:46:24 +02:00
moanos 328f64aa51 refactor(bulma): various improvements on AN display in list 2025-05-22 17:17:39 +02:00
moanos fdf4e79a69 refactor(bulma): use main-thumbnail view for AN photos 2025-05-18 14:27:00 +02:00
moanos bbc8732112 refactor(bulma): use generic gallery to avoid having multiple same ids 2025-05-18 14:23:09 +02:00
moanos 17dbe85219 refactor(bulma): Fix urls of images 2025-05-18 14:08:58 +02:00
moanos 3dc011a22c refactor(bulma): Move animals to separate column 2025-05-18 14:07:31 +02:00
moanos f5b89456ab refactor(bulma): make thumbnail row use same width images and crop when necessary 2025-05-18 13:16:47 +02:00
moanos 2e4f63b250 refactor(bulma): Work on images 2025-05-18 13:05:37 +02:00
moanos 3b261ff240 feat: Add widget tweaks dependency for bulma forms 2025-05-14 19:25:40 +02:00
moanos f06b00fb9d feat: Add image upload form in bulma 2025-05-14 19:25:18 +02:00
moanos 88987a973e feat: Manually craft the add adoption form to work with bulma
ci/woodpecker/push/woodpecker Pipeline was successful
2025-05-11 16:21:06 +02:00
moanos 93ffbe09af feat: Link to new layout 2025-05-11 13:48:25 +02:00
moanos e11848ea72 feat: Add js to close notifications 2025-05-11 13:43:26 +02:00
moanos 8bc9d12bfa feat: Add basic bulma form to add adoptions 2025-05-11 13:43:10 +02:00
moanos 1dbfdccb89 feat: Add rules to TOS page 2025-05-11 13:42:51 +02:00
moanos f085f5dcf5 feat: Remove leftover span 2025-05-11 09:11:07 +02:00
moanos 33579e8446 feat: Allow searching for rescue orgs 2025-05-11 08:55:29 +02:00
moanos a852da365f feat: Remove about us from main menu 2025-05-10 14:13:00 +02:00
moanos b53095ae17 feat: Add onpagers for imprint, privacy and terms of service 2025-05-10 13:35:53 +02:00
moanos 3d7780e0ba feat: style 2025-05-10 13:15:37 +02:00
moanos 478636bd98 feat: add bulma about 2025-05-10 13:12:58 +02:00
moanos d9ebee1e07 feat: add bulma comment section 2025-05-10 12:02:11 +02:00
moanos 23e154bce6 feat: further restructure search 2025-05-10 09:26:49 +02:00
moanos 5624f59258 feat: Add image for animal selter 2025-05-10 08:56:00 +02:00
moanos 56df942dd0 feat: map 2025-05-10 08:55:23 +02:00
moanos 2dcb5fbf88 feat: Structure list a bit more 2025-05-09 21:54:06 +02:00
moanos 7a84b470f9 feat: Structure list a bit more 2025-05-09 21:53:59 +02:00
moanos 76232b7a0f feat: make sure maps extends over all available height, round corners 2025-05-09 21:16:54 +02:00
moanos 349af16075 feat: pad content 2025-05-09 21:16:17 +02:00
moanos 8641bead80 feat: make display of location nicer 2025-05-09 21:03:21 +02:00
moanos eb930b71d6 fix: remove debug statements 2025-05-09 21:03:00 +02:00
moanos ae4ba06abf fix: use normal partial 2025-05-09 21:01:33 +02:00
moanos a2e237a81f feat: Make card heading more noticeable 2025-05-09 20:58:23 +02:00
moanos f90c8c7e8c feat: Add name to header 2025-05-09 20:57:58 +02:00
moanos c316c74aff feat: fix map title 2025-05-09 20:41:04 +02:00
moanos 93dd0ae4f6 feat: Add bulma map 2025-05-09 20:40:54 +02:00
moanos f79bb355cf feat: Link to bulma url 2025-05-09 20:20:04 +02:00
moanos 45a534a042 feat: Add index page in bulma 2025-05-09 20:12:12 +02:00
moanos 2106a3423f feat: Remove compass and add fullscreen option 2025-05-09 18:34:09 +02:00
moanos d3f7274e92 feat: Restructure search and add blocks 2025-05-09 18:27:52 +02:00
moanos 5f576896b7 feat: Add custom form rendering to support bulma 2025-05-09 18:15:17 +02:00
moanos 4a3cbfb8b0 feat: wrap blocks 2025-05-09 17:15:16 +02:00
moanos 3e93fe1a7a feat: Remove redundant heading "Pictures" 2025-05-09 17:14:55 +02:00
moanos 965e055ef1 feat: Add bulma search 2025-05-09 17:14:34 +02:00
moanos 13a0da6e46 feat: Move sex overview to partial 2025-05-09 17:13:31 +02:00
moanos 1bb05dbf1c feat: Add tags for sex 2025-05-01 18:41:35 +02:00
moanos 4c9c1e13a5 feat: further redesign 2025-05-01 18:15:25 +02:00
moanos 99cde15966 feat: Add filter for important locations 2025-04-28 22:46:18 +02:00
moanos f2edc23e75 feat: Make cities visible at lower zoomlevels 2025-04-27 23:34:13 +02:00
moanos 8aab4a13ae feat: Exchange pin with circle
Allows to still see a cities label
2025-04-27 23:33:41 +02:00
moanos 226102ccaf feat: Display proper 404 when location is not found 2025-04-27 15:05:22 +02:00
moanos 3d088c55d7 fix: Adjust to use new versatiles structure
See https://docs.versatiles.org/compendium/specification_frontend.html
2025-04-27 14:31:53 +02:00
moanos bb14a346cb feat: Add important locations to search around 2025-04-27 14:06:17 +02:00
moanos f387930dee feat: Allow longer placids with no restrictions on int 2025-04-27 00:21:58 +02:00
moanos fe63e3b25c feat: Link organization Website directly 2025-04-26 23:03:02 +02:00
moanos 23adeb06e6 feat: Allow getting and setting photos with ANs in API 2025-04-25 19:23:45 +02:00
moanos c1bd458c80 feat: Allow adding locations and organizations to ANs in API 2025-04-25 19:18:06 +02:00
moanos 2a1d4178d7 feat: Allow creating locations via API 2025-04-24 22:35:38 +02:00
moanos f9a37b299d feat: Extend location model to allow specifying address 2025-04-24 19:43:48 +02:00
moanos 9950e87501 feat: Make use of footer items 2025-04-24 18:50:48 +02:00
moanos eff1ba6513 feat: Make use of footer items 2025-04-23 21:14:16 +02:00
moanos bb085aa9a8 feat: Add basic bulma version of comment form 2025-04-23 21:12:22 +02:00
moanos b0dc0f9d78 feat: Make photos to be in card 2025-04-23 20:56:19 +02:00
moanos d1a51b019c feat: Make columns to stack vertically on mobile 2025-04-23 20:53:26 +02:00
moanos b7fade55fb feat: Make header of description card header title 2025-04-23 20:20:24 +02:00
moanos 79461518a3 feat: add bulma animal cards 2025-04-10 15:12:39 +02:00
moanos 8059d5d23f feat: add photoswipe to adoption notice detail page 2025-04-10 15:12:21 +02:00
moanos 3098eacfb4 feat: only exclude the static folder in root from VSC 2025-04-10 15:11:26 +02:00
moanos f3d1e1c203 feat: make headings strong 2025-04-07 21:32:46 +02:00
moanos e6a985ddfa feat: Add initial bulma version of adoption notice detail page 2025-04-07 21:30:14 +02:00
moanos 388cc327be feat: Add cards 2025-04-06 10:48:03 +02:00
moanos 13adc695f6 feat: Style headings and add change langueg form 2025-04-06 10:25:37 +02:00
moanos f2c7943247 fix: Add missing div 2025-04-06 10:03:52 +02:00
moanos 112fd52864 feat: Add footer 2025-04-06 10:03:35 +02:00
moanos 8279385966 feat: Rename bulma styleguide and add navigation 2025-04-06 09:05:50 +02:00
moanos 1a9692949f feat: add alt text to logo 2025-04-06 09:05:00 +02:00
moanos e7af49b309 feat: add optional address data to location 2025-04-06 08:38:34 +02:00
moanos b822914db3 feat: Allow updating existing rescue organizations 2025-03-21 16:11:58 +01:00
moanos 9ad33efe08 feat: Allow filtering for external object id and source 2025-03-21 15:53:58 +01:00
moanos bd8f9fc1b7 feat: Ensure External object identifier and external source identifier are unique together 2025-03-21 15:26:01 +01:00
moanos 4a2c18be4d feat: replace upload script with version of g 2025-03-21 12:56:34 +01:00
moanos 479aba0195 fix: Adjust maplibre paths for versatiles 0.15.X
See https://github.com/versatiles-org/versatiles-docker/issues/16
2025-03-21 00:51:58 +01:00
moanos 1299fcac84 refactor: Rename 2025-03-21 00:29:23 +01:00
moanos 884a07f87b feat: Use choices and fix bug where default was not honored 2025-03-21 00:28:15 +01:00
moanos 6557e9f9eb feat: Auto-add location to rescue org 2025-03-20 23:26:16 +01:00
moanos 602cef1302 refactor: use bulma.min.css 2025-03-20 19:20:40 +01:00
moanos b400db603a refactor: Remove js part -> model 2025-03-20 19:12:32 +01:00
moanos 0397311f6e feat: Add bulma and base for bulma styleguide 2025-03-20 19:12:32 +01:00
moanos abce89c829 feat: Add bulma and base for bulma styleguide 2025-03-20 18:58:40 +01:00
moanos bbad63a460 fix: Adjust site language based on selected language 2025-03-20 18:34:25 +01:00
moanos d940630086 refactor: formatting 2025-03-17 21:08:41 +01:00
moanos 37ecf28f2f feat: add link to original content
ci/woodpecker/push/woodpecker Pipeline was successful
2025-03-10 22:12:35 +01:00
moanos 12d5a976cc feat: add link to original content 2025-03-10 22:12:28 +01:00
moanos 9086e2e75b fix: Show name of reported content 2025-03-10 22:04:14 +01:00
moanos 3607eb0e4e feat: Add message when no comment is added to report 2025-03-10 21:09:06 +01:00
moanos 3daf83d725 tests: Fix testing for edit buttons 2025-03-09 18:05:29 +01:00
moanos 5ad0cb74cc feat: Only show edit buttons when mod 2025-03-09 18:05:15 +01:00
moanos 9ae64e8cb1 feat: Auto add now the date oflast checked
ci/woodpecker/push/woodpecker Pipeline was successful
2025-03-09 17:46:13 +01:00
moanos 1b5a0c71e0 feat: Add rescue organization check 2025-03-09 09:46:56 +01:00
moanos 4d4f11c479 feat: Make string translatable 2025-03-09 09:16:37 +01:00
moanos 835c89d1d4 test: Add test for details of comment reports 2025-02-05 22:32:18 +01:00
moanos 46bf07dd8d test: Add test for reporting comments anon and logged in 2025-02-05 22:11:59 +01:00
moanos f557672586 test: Add test for reporting rules anon 2025-02-05 20:17:36 +01:00
moanos 4e27e1be7f test: Add test for about page and for reporting rules logged in 2025-01-27 18:51:14 +01:00
moanos 6d390ad21e test: Add test for (un)subscribes of searches 2025-01-26 20:19:18 +01:00
moanos 2f2543160e test: Add test for unauthenticated (un) subscribes of searches 2025-01-26 19:01:39 +01:00
moanos 64a9db133e feat: default to photon for geocoding 2025-01-26 18:16:39 +01:00
moanos 712c3d32f3 feat: Add styleguide setup 2025-01-26 18:16:39 +01:00
moanosandGitHub 8998bbdf6d Merge pull request #9 from moan0s/shelter-fixes
Add script to upload data of animal shelters
2025-01-26 18:04:48 +01:00
moanos ff31caa139 refactor: Move script to dedicated folder 2025-01-26 18:01:38 +01:00
moanos ad06829c31 feat: Add debug information 2025-01-26 18:00:23 +01:00
moanos 03a48da355 feat: Make instance and secrets CLI argument 2025-01-26 17:59:48 +01:00
moanos 885bed888d feat: Raise connection error upon unexpected error
This e.g. makes sure the API is not bombarded with unauthorized calls if the token is wrong
2025-01-26 17:07:50 +01:00
moanos 0051cb07c9 refactor: formatting 2025-01-26 17:06:41 +01:00
moanos 8858cff9cf fix: Construct necessary location string
I'd be better to directly create a location here but I for now want to make as little modifications as possible
2025-01-26 17:05:51 +01:00
moanos 70e2af6172 fix: fix key 2025-01-26 17:04:56 +01:00
moanos 461abd2e46 fix: don't try to save owner 2025-01-26 17:04:17 +01:00
SalilandGitHub d7269106db Added - animal_shelter Get Data
Import all German animal shelters
2025-01-24 12:22:28 +05:30
moanosandGitHub 77fb99a527 Merge pull request #6 from Deadpool2000/patch-1
Create robots.txt
2025-01-22 20:28:39 +01:00
moanos 38a56daa24 feat: Add sitemap 2025-01-22 11:01:31 +01:00
SalilandGitHub ac0749797f Create robots.txt
- robots.txt file added in src/fellchensammlung/static/
2025-01-22 10:03:30 +05:30
moanos f193f7d7ca test: Add tests for AN edit 2025-01-19 23:11:07 +01:00
moanos 43657e0862 test: Add tests for comment 2025-01-19 22:07:21 +01:00
moanos 68ad366f74 test: Add tests for comment 2025-01-19 09:00:53 +01:00
moanos 350d2c5da9 feat: Return HTTP response 2025-01-19 09:00:26 +01:00
moanos 462bb8f485 refactor: formatting 2025-01-18 21:43:37 +01:00
moanos ea4d15b99a tests: Add test for index view 2025-01-18 21:43:00 +01:00
moanos de30dfcb8b tests: Add test for unsubscribe 2025-01-18 21:39:45 +01:00
moanos 36a979954c feat: make sure owner also gets notified, add test 2025-01-18 18:53:55 +01:00
moanos 71ef17dc97 feat: Move pytest, coverage and model bakery to develop dependencies 2025-01-18 18:30:02 +01:00
moanos 206cd282e6 feat: Add coverage report instructions 2025-01-18 18:29:04 +01:00
moanos e399346c3e feat: Add tests for subscription functionality 2025-01-18 16:17:22 +01:00
moanos 929c6dfff0 feat: Add registration button 2025-01-18 15:47:07 +01:00
moanos 841b57fea2 feat: Make sure subscribe leads to login flow 2025-01-18 15:25:27 +01:00
moanos 9e5446ff1d feat: Test adding a adoption as user 2025-01-18 15:22:08 +01:00
moanos 3b79809b8c docs: various 2025-01-18 09:07:33 +01:00
moanos 53e6db3655 refactor: Remove print 2025-01-14 07:31:00 +01:00
moanos 424f91e919 feat: Add a timestamp for when a notification is read 2025-01-14 07:30:36 +01:00
moanos 84ce5f54b2 refactor: Remove unnecessary print 2025-01-14 07:27:03 +01:00
moanos a7e85212c0 feat: Add verbose names 2025-01-11 14:19:23 +01:00
moanos f1b3b660ff feat: Add notification for unchecked ANs 2025-01-11 14:19:02 +01:00
moanos 26cb60c1c8 style: make registration flow use cards 2025-01-11 11:35:05 +01:00
moanos 69e58f1e0a docs: add changelog for 0.4.0 2025-01-11 11:18:22 +01:00
moanos 5c33ac3833 meta: bump version to 0.4.0 2025-01-11 11:03:07 +01:00
moanos fccfd59ea3 refactor: change order for reading convenience 2025-01-11 11:02:42 +01:00
moanos 50897b6d35 fix: remove double , when searching for city 2025-01-09 23:39:23 +01:00
moanos 8edfe8c401 feat: Re-add warning if place not found 2025-01-09 23:35:07 +01:00
moanos 0d82dba414 fix: Remove debug code 2025-01-09 23:28:28 +01:00
moanos 2dc038dfef feat: Show search radius only if a search center is given and correct zoom level if no location is given 2025-01-09 23:27:33 +01:00
moanos c46a943c7f feat: make map circle alot more transparent 2025-01-09 23:19:02 +01:00
moanos 9f3592e64b refactor: remove pulsing dot 2025-01-09 23:17:12 +01:00
moanos bc1f4e7ab7 feat: construct search results better 2025-01-09 23:15:10 +01:00
moanos a2ef91e89a feat: Handle missing name 2025-01-09 23:14:41 +01:00
moanos 91d740511d feat: Search language specific 2025-01-09 22:39:34 +01:00
moanos c6af3e8d04 feat: Only show existing data 2025-01-09 19:26:33 +01:00
moanos 0c94049e21 fix: Fix bug for ANs that have not been checked for more that three months 2025-01-09 06:59:09 +01:00
moanos 29f1d2f0f2 feat: Fill search query with detailed information to make sure photon will get the same location 2025-01-09 06:41:54 +01:00
moanos 2578e96b32 feat: Remove legacy feedback to users 2025-01-09 06:34:50 +01:00
moanos 907ed583cd feat: Add tooltip to report link 2025-01-09 06:30:22 +01:00
moanos da51007b77 feat: Add tooltip for unsubscribe 2025-01-09 06:22:01 +01:00
moanos 087f58c9ac feat: Style buttons as in other forms 2025-01-09 06:14:45 +01:00
moanos 860da7f06a feat: Use unified button layout 2025-01-09 06:12:21 +01:00
moanos 457bee1ede feat: Add verbose names to report 2025-01-09 06:10:44 +01:00
moanos 3b37b5f588 docs: label 2025-01-09 06:07:54 +01:00
moanos 6229f0f8a2 feat: Label ANs as active/inactive 2025-01-09 06:06:32 +01:00
moanos b2a3d910d9 feat: Make geocoding API configurable 2025-01-08 17:57:48 +01:00
moanos 33848cbe15 feat: mention photon in readme 2025-01-08 09:29:01 +01:00
moanos cc97fe32aa feat: add search-as-you-type functionality 2025-01-08 09:18:48 +01:00
moanos 4576ac68e0 feat: move search location not found error to the place where the location would have been shown 2025-01-07 15:59:22 +01:00
moanos 7c076e0bc3 feat: add logging and string representation 2025-01-07 15:04:43 +01:00
moanos 74f54c7b31 fix: make sure that search radius and pins are not cast to int 2025-01-07 15:04:23 +01:00
moanos 87777cd5a4 feat: add pin of map center 2025-01-07 14:56:23 +01:00
moanos eee4cdf86b feat: Show location when searching 2025-01-07 14:37:02 +01:00
moanos b2d5265f7e feat: Use photon for querying 2025-01-07 12:48:01 +01:00
moanos d4af2d88b4 refactor: Remove unused function 2025-01-07 12:47:26 +01:00
moanos 8b4f5713e3 test: Use Location Proxy for test 2025-01-07 12:46:19 +01:00
moanos 4bff268537 fix: fix test 2025-01-07 12:45:51 +01:00
moanos 57da42e4bd feat: allow markdown in animal description 2025-01-07 09:19:41 +01:00
moanos 2864d27a7f refactor: typo 2025-01-06 10:21:00 +01:00
moanos 0a73b5099e refactor: Remove unnecessary div 2025-01-06 10:20:41 +01:00
moanos e3fb981542 fix: Don't squash font into each other in card 2025-01-06 10:20:16 +01:00
moanos 5e80d75c91 feat: Add overview page of animal shelters 2025-01-06 09:02:07 +01:00
moanos e3833b4505 feat: Show position of shelter on the map 2025-01-06 08:36:51 +01:00
moanos ab837ee80e feat: Add contact information to rescue org 2025-01-05 22:55:26 +01:00
moanos f6c1224dde feat: Group buttons as edit buttons 2025-01-05 21:54:55 +01:00
moanos a78d671b6d feat(accessibility): use h1 only once per site 2025-01-05 21:35:29 +01:00
moanos fb9c78d96a feat: Add species specific URL to allow faster checking if new animals exist in this rescue org 2025-01-05 21:04:27 +01:00
moanos 4ef9da953c feat: Add annotation for API 2025-01-05 20:22:21 +01:00
moanos aefeffd63a feat: Add post method to create rescue orgs 2025-01-05 19:20:34 +01:00
moanos 81cc5cd53d feat: Add external source and object identifier 2025-01-05 19:20:05 +01:00
moanos 002dded0d5 feat: Add Spectacutlar API schema generation 2025-01-05 16:55:23 +01:00
moanos ad6e2f4e17 fix: translate 2025-01-05 09:17:43 +01:00
moanos 160e7166f8 feat: reduce heading spacing, adjust cards 2025-01-04 11:30:36 +01:00
moanos 867319fe9a feat: space card containers 2025-01-04 11:24:57 +01:00
moanos 13b67c1248 feat: Add last checked to updatequeue 2025-01-04 09:52:22 +01:00
moanos 4c4cf4afea fix: remove debug message 2025-01-04 09:50:42 +01:00
moanos 5f742c60db fix: fix missing d
otherwise throws unsupported format character 'W' (0x57) at index 13
2025-01-04 09:50:32 +01:00
moanos 568874e6dd feat: Make edit buttons flex 2025-01-04 09:48:16 +01:00
moanos 561a30b7ab feat: Represent last checked more human-readable 2025-01-04 09:48:05 +01:00
moanos a8c837e9f6 feat: Make sure heading fills complete line 2025-01-04 09:06:58 +01:00
moanos a75cacea66 feat: Make table in adoption notice responsive 2025-01-03 22:04:19 +01:00
moanos b1e092769f fix: Apply vertical align to all children 2025-01-03 20:19:55 +01:00
moanos 5a93a1678c refactor: remove unnecessary class 2025-01-03 20:19:34 +01:00
moanos 28772e1f74 feat: Restyle using a proper container to group elements and not just put them in the heading 2025-01-03 19:04:32 +01:00
moanos 1f3c3ecaef feat: Add top and bottom option to tooltip 2025-01-03 18:46:22 +01:00
moanos ab1e6a94d1 feat: add tooltip to subscribe bell 2025-01-03 18:32:34 +01:00
moanos 299653b53b fix: syntax 2025-01-03 11:40:03 +01:00
moanos fe9352e628 feat: Add tooltip explaining the meaning of the checkmark 2025-01-03 11:18:10 +01:00
moanos 9fec95bd2e feat: add trusted checkmark 2025-01-02 19:16:22 +01:00
moanos 8e7cdafee0 fix: deal with undefined 2025-01-02 11:14:34 +01:00
moanos 6e2a2a1d5e fix: use builtin function
https://docs.djangoproject.com/en/5.1/topics/auth/default/
2025-01-02 00:16:42 +01:00
moanos 5197875431 refactor: formatting 2025-01-01 23:52:54 +01:00
moanos d05bd45cf4 feat: restyle search subscriptions 2025-01-01 23:52:44 +01:00
moanos 0afb2bb0ce feat: add list of search subscriptions to user profile 2025-01-01 23:29:23 +01:00
moanos d17fcc1da2 feat: add updated_at and created_at to search subscription 2025-01-01 23:05:22 +01:00
moanos c508bc2cd1 test: fix test after map was included 2025-01-01 22:56:01 +01:00
moanos 20872e547b fix: add turf to VC 2025-01-01 21:02:10 +01:00
moanos 25b748d2be fix: Style buttons 2025-01-01 20:58:50 +01:00
moanos 1536bb302a fix: handle missing radius 2025-01-01 20:55:50 +01:00
moanos d4ef706734 feat: reorder search options 2025-01-01 20:55:35 +01:00
moanos 3bdce18e9e feat: make zoom level dependent on search radius 2025-01-01 20:26:59 +01:00
moanos 8b4488484d feat: show radius and center map 2025-01-01 20:14:07 +01:00
moanos 3881a4f3b4 feat: add map to search 2025-01-01 19:48:33 +01:00
moanos 2dbd908f4c feat: add method to search active ANs 2025-01-01 19:43:50 +01:00
moanos 9d0eed5915 test: Add e2e test for distance 2025-01-01 18:59:01 +01:00
moanos ee12bb5286 feat: add debugging statements 2025-01-01 17:52:46 +01:00
moanos 5669c822b9 test: fix test search 2025-01-01 17:52:28 +01:00
moanos c1c4af6571 feat: Add logging 2025-01-01 17:35:27 +01:00
moanos 164ba7def2 feat: Add test for adoption_notice_fits_search 2025-01-01 17:23:38 +01:00
moanos 7035b1642e feat: streamline search_from pattern 2025-01-01 17:22:26 +01:00
moanos b6fc5c634f feat: add debugging messages 2025-01-01 17:21:09 +01:00
moanos 0dfbd614ab fix: remove deprecated search position 2025-01-01 17:20:44 +01:00
moanos 2730ff3f51 refactor: Create shared task for post-AN stuff 2025-01-01 14:35:40 +01:00
moanos fef211b2d0 feat: Add logging 2025-01-01 14:34:38 +01:00
moanos f2e2599561 fix: Make sure that subscribed search is only checked when user is authenticated 2025-01-01 09:47:07 +01:00
moanos a9c0f628f7 refactor: remove print 2025-01-01 09:46:20 +01:00
moanos e2adb20231 feat: re-add locate to ease use 2025-01-01 09:44:56 +01:00
moanos e8b3bf6516 fix: fix general notify 2025-01-01 00:57:37 +01:00
moanos 3306f3e783 feat: Add notification for newly created ANs 2025-01-01 00:30:14 +01:00
moanos b993621773 feat: add unsubscribe functionality 2024-12-31 16:25:18 +01:00
moanos 3816290eb7 fix: Location matching logic 2024-12-31 15:40:57 +01:00
moanos 399ecf73ad feat: use location proxy to make Location search interface more intuitive 2024-12-31 15:40:33 +01:00
moanos 8e2c0e857c feat: Show subscribe/unsubscribe button depending on the user having this search already subscribed 2024-12-31 13:48:44 +01:00
moanos 3c7dcb4c51 feat: Allow location to be null in SearchSubscription 2024-12-31 13:47:38 +01:00
moanos 9e1ec1711b fix: Make Search and Search subscription are not the same if Search is not localized 2024-12-31 13:38:06 +01:00
moanos bae4ee3d22 feat: Do not show subscribe button when not yetsearched 2024-12-31 13:37:31 +01:00
moanos 280eb83056 feat: Add function to convert a search subscription to a search 2024-12-31 13:28:41 +01:00
moanos fca5879aeb test: Add tests for search equality 2024-12-31 13:28:14 +01:00
moanos 373a44c9da fix: notify only subscribers where the AN fits 2024-12-31 13:27:35 +01:00
moanos 674645c65c feat: Add string representation of search 2024-12-31 13:26:38 +01:00
moanos c2b3ff2395 refactor: Rename radius to streamline although it would be a better description 2024-12-31 13:25:43 +01:00
moanos d6740eb302 test: adjust to reflect changed field name 2024-12-31 13:14:13 +01:00
moanos 35a54474b4 test: fix name 2024-12-31 12:55:46 +01:00
moanos 6723dad4bd test: add owner to test to prevent random owner generation 2024-12-31 12:55:13 +01:00
moanos b51d04ffd1 feat: Use label in string representation 2024-12-31 12:14:04 +01:00
moanos a965f26d48 fix: use Sex choices with all 2024-12-31 11:38:52 +01:00
moanos 364a6f32f4 refactor: Typo 2024-12-31 11:35:29 +01:00
moanos 533142461a feat: Add string representation of SearchSubscriptions 2024-12-31 10:20:38 +01:00
moanos 481635ac4e feat: Add contributing doc 2024-12-31 10:18:18 +01:00
moanos be6c30cb33 feat: Add SearchSubscriptions to admin 2024-12-31 10:03:56 +01:00
moanos a617137fb0 fix: form submit must have name subscribe_to_search to trigger 2024-12-31 10:03:39 +01:00
moanos 8299162a77 formatting 2024-12-26 20:27:01 +01:00
moanos 085162d802 feat: Add is_subscribed method for searches 2024-12-26 20:24:35 +01:00
moanos 27b7e47f18 feat: Add SearchSubscriptions 2024-12-26 20:24:10 +01:00
moanos be97ac32fb refactor: Move search into class 2024-12-26 16:55:37 +01:00
moanos 9ea00655d4 refactor: Use integer choice for search 2024-12-24 10:02:08 +01:00
moanos 9fffbffdb7 feat: add FAQ section to about 2024-12-24 09:05:11 +01:00
moanos 44cf2936d1 ui: Make button more buttony 2024-12-24 09:01:13 +01:00
moanos 579f59580c feat: Redirect user to adoption notice after adding photo to an animal 2024-12-18 10:17:27 +01:00
moanos 241841bc9b feat: Redirect user to adoption notice after editing an animal 2024-12-18 10:15:00 +01:00
moanos 78a6440f63 feat: Re-add text decoration for accessibility 2024-12-17 23:02:30 +01:00
moanos 9d521b0129 feat: Set title on user page 2024-12-17 22:55:46 +01:00
moanos 39079c3c8e feat: Add fallback if first and lastname is not defined 2024-12-17 22:55:30 +01:00
moanos 999c1a81b8 feat: Restructure user page 2024-12-17 22:51:04 +01:00
moanos 5a4720c41c feat: Show "No notifications" message 2024-12-17 22:46:27 +01:00
moanos 858c6d4468 feat: Use real toggle 2024-12-17 22:46:01 +01:00
moanos 4b45b01e2a feat: Add toggle for e-mail notifications 2024-12-17 21:42:10 +01:00
moanos d0060ecf5e feat: Fully define place_not_found 2024-12-17 20:14:34 +01:00
moanos d1eeaafc42 feat: Also search for description, internal comment and location of rescue orgs
icontains is default and therefore ommitted
https://docs.djangoproject.com/en/5.1/ref/contrib/admin/#django.contrib.admin.ModelAdmin.search_fields
2024-12-17 20:14:14 +01:00
moanos 9b824bc326 feat: Automatically subscribe user that created AN to AN 2024-12-14 13:24:51 +01:00
moanos 44f05cbb7d feat: Notify all subscribers of a adoption 2024-12-14 13:03:00 +01:00
moanos 0e4e531414 feat: Add 404 deactivation to instance health check 2024-12-14 09:32:37 +01:00
moanos 6a7b3f19e9 feat: Link Adoption Notice on notification 2024-12-14 09:31:46 +01:00
moanos ec9f5b305c feat: Create notifications for 404 deactivation 2024-12-14 09:31:06 +01:00
moanos e858f61b3f feat: Translate Species 2024-12-14 08:41:00 +01:00
moanos a04270718f feat: Make announcement collapsable 2024-12-14 08:28:37 +01:00
moanos a4f895de81 feat: add admin for Base Notification 2024-12-12 06:40:11 +01:00
moanos b2d0e783be feat: make sure report flag stays in its lane
very demure, very mindful
2024-12-11 22:45:45 +01:00
moanos 4f5022e140 feat: Use card concept for about site 2024-11-30 09:37:14 +01:00
moanos 5771968981 refactor: remove unnecessary imports 2024-11-30 09:31:14 +01:00
moanos b63b87872b feat: Improve accessibility by using correct heading layer 2024-11-30 09:31:00 +01:00
moanos 1594b754cb docs: Fix api endpoint
ci/woodpecker/push/woodpecker Pipeline was successful
2024-11-25 18:36:33 +01:00
moanos 8ec27191b6 docs: Document API methods
ci/woodpecker/push/woodpecker Pipeline was successful
2024-11-25 16:27:42 +01:00
moanos c1332ee1f0 feat: Add API methods for Animals, Images, Species andRescue orgs
ci/woodpecker/push/woodpecker Pipeline was successful
2024-11-24 22:29:19 +01:00
moanos f6240a7189 Merge branch 'ui_mobile' 2024-11-24 15:58:32 +01:00
moanos 7a02774a29 feat: make menu more spaced but remove border 2024-11-24 15:58:22 +01:00
moanos 8945fdc0f4 feat: Make header without radius so that menu and header don't have hole 2024-11-24 15:50:10 +01:00
moanos 9f0a18ad91 feat: Make sure main menu is shown in the middle when enabled 2024-11-24 14:21:52 +01:00
moanos e7f26dd23a feat: integrate profile card seamlessly 2024-11-24 14:13:41 +01:00
moanos fc5b1391df feat: align items, hide unnecessary for mobile 2024-11-24 12:46:46 +01:00
moanos 70bf8e2053 feat: re-add profile card 2024-11-24 12:46:30 +01:00
moanos caf98ba60b docs: Add two endpoints
ci/woodpecker/push/woodpecker Pipeline was successful
2024-11-24 11:51:37 +01:00
moanos d7e466050a docs: remove not implemented warning 2024-11-24 11:50:34 +01:00
moanos 34b707ef20 feat: add expandable navigation, sacrifice right header for now 2024-11-24 11:30:12 +01:00
moanos 064a9bf83a feat: Streamline serializer use, check trust level, add log 2024-11-23 14:50:26 +01:00
moanos 93070a3bcd refactor: formatting 2024-11-22 21:55:25 +01:00
moanos 23c35fe7dd refactor: Naming 2024-11-22 18:50:59 +01:00
moanos d2542060a1 feat: Allow to bulk-activate ANs in the admin interface 2024-11-22 18:50:46 +01:00
moanos 89f74cb709 fix: Save date of last checked
Otherwise, ANs will get deactivated in the next night again
2024-11-22 18:50:23 +01:00
moanos ec38012ecb test: fix test by setting date of last checked correctly 2024-11-22 18:49:19 +01:00
moanos 72d45a4f47 refactor: typo 2024-11-22 18:48:57 +01:00
300 changed files with 20801 additions and 5658 deletions
+4
View File
@@ -0,0 +1,4 @@
[run]
omit =
*/migrations/*
*/tests/*
+9 -1
View File
@@ -2,11 +2,18 @@
# Database
notfellchen
*.sq3
# Geojson from imports
*.geojson
# Media storage
static
/static
media
# Compiled CSS
/src/fellchensammlung/static/fellchensammlung/css/main.css
/src/fellchensammlung/static/fellchensammlung/css/main.css.map
# Byte-compiled / optimized / DLL files
__pycache__/
@@ -161,3 +168,4 @@ dmypy.json
# Cython debug symbols
cython_debug/
/node_modules/
+15
View 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/)
+4 -5
View File
@@ -1,6 +1,6 @@
FROM python:3.11-slim
# Use 3.11 to avoid django.core.exceptions.ImproperlyConfigured: Error loading psycopg2 or psycopg module
MAINTAINER Julian-Samuel Gebühr
LABEL org.opencontainers.image.authors="Julian-Samuel Gebühr"
ENV DOCKER_BUILD=true
@@ -9,15 +9,14 @@ RUN apt install gettext -y
RUN apt install libpq-dev gcc -y
COPY . /app
WORKDIR /app
RUN mkdir /app/data
RUN mkdir /app/data/static
RUN mkdir /app/data/static -p
RUN mkdir /app/data/media
RUN pip install -e . # Without the -e the library static folder will not be copied by collectstatic!
RUN pip install --no-cache-dir -e . # Without the -e the library static folder will not be copied by collectstatic!
RUN nf collectstatic --noinput
RUN nf compilemessages --ignore venv
COPY docker/notfellchen.bash /bin/notfellchen
COPY docker/entrypoint.sh /bin/notfellchen
EXPOSE 7345
CMD ["notfellchen"]
+82 -8
View File
@@ -2,7 +2,7 @@
[notfellchen.org](https://notfellchen.org) ist eine Sammelstelle für Tier-Vermittlungen. Die Idee entstand, da in der
deutschsprachigen Rattencommunity ein wilder Mix aus Websites, Foren und Facebookgruppen besteht die Ratten vermitteln.
Diese Website soll die bestehende Communities NICHT ersetzten, jedoch ermöglichen, dass Menschen die Ratten aufnehmen
Diese Website soll die bestehende Communitys NICHT ersetzten, jedoch ermöglichen, dass Menschen die Ratten aufnehmen
wollen Informationen einfach finden und nicht bereits in jeder Gruppe sein müssen.
Wir nehmen Angebote auf die
@@ -57,10 +57,50 @@ Therefore, a solution is used where a number of predefined texts per site are su
# Developer Notes
## Getting started
### Clone the project
```
git clone https://codeberg.org/moanos/notfellchen.git
```
### Install dependencies
```
pip install -e '.[all]'
```
### Create the database
```
nf migrate
```
Because of a wired bug the initial migrations must run two times as the first time the permissions
for `create_active_adoption_notice` are created but can not yet be accessed and on the second time this permission will
be added to groups.
### Start the server
```
nf runserver
```
### Build the docs
```
sphinx-autobuild ./docs ./docs/_build/html
```
## Styling
Bulma is used for styling, including related SCSS. All styles should eventually be migrated to SCSS.
Use `npm run build-bulma` to generate the css file from SCSS.
You can use `npm start` during development so that the file is re-generated upon change.
## Docker
Build latest image
@@ -77,20 +117,37 @@ docker push moanos/notfellchen:latest
docker run -p8000:7345 moanos/notfellchen:latest
```
## Testing
Tests can be run with
```zsh
nf test src
```
If you want to report on code coverage run
```zsh
coverage run --source='.' src/manage.py test src
```
and
```
coverage report
```
## Geocoding
Geocoding services (search map data by name, address or postcode) are provided via the
[Nominatim](https://nominatim.org/) API, powered by [OpenStreetMap](https://openstreetmap.org) data. Notfellchen uses
a selfhosted Nominatim instance to avoid overburdening the publicly hosted instance. Due to ressource constraints
geocoding is only supported for Germany right now.
ToDos
* [ ] Implement a report that shows the number of location strings that could not be converted into a location
* [x] Add a management command to re-query location strings to fill location
either [Nominatim](https://nominatim.org/) or [photon](https://github.com/komoot/photon) API, powered by [OpenStreetMap](https://openstreetmap.org) data.
Notfellchen uses a selfhosted Photon instance to avoid overburdening the publicly hosted instance.
## Maps
The map on the main homepage is powered by [Versatiles](https://versatiles.org), and rendered using [Maplibre](https://maplibre.org/).
The Versatiles server is self-hosted and does not send data to third parties.
## Translation
@@ -125,3 +182,20 @@ Start beat
```zsh
celery -A notfellchen.celery beat
```
# Contributing
This project is currently mainly developed by me, moanos. I'd like that to change and will be very happy for contributions
and shared responsibilities. Some ideas where you can look for contributing first
* UI improvements: Since a major redesign I'm much happier but the UI could use many, many little tweaks
* Docker: If you know how to build a docker container that is a) smaller or b) utilizes staged builds this would be amazing. Any improvement welcome
* Testing: Writing tests is always welcome, and it's likely you discover a few bugs
I'm also very happy for all other contributions. Before you do large refactoring efforts or features, best write a short
issue for it before you spend a lot of work.
Send PRs either to [codeberg](https://codeberg.org/moanos/notfellchen) (preferred) or [GitHub](https://github.com/moan0s/notfellchen).
CI (currently only for documentation) runs via [git.hyteck.de](https://git.hyteck.de), you can also ask moanos for an account there.
Also welcome are new issues with suggestions or bugs and additions to the documentation.
+83 -6
View File
@@ -2,10 +2,10 @@
API Documentation
*****************
The Notfellchen API serves the purpose of supporting 3rd-person applications and anything you can think of basically.
The Notfellchen API serves the purpose of supporting 3rd-person applications, whether you want to display data in a custom format or add data from other sources.
.. warning::
The current API is limited in it's functionality. I you miss a specific feature please contact the developer!
The current API is limited in it's functionality. I you miss a specific feature please contact the developers!
API Access
==========
@@ -14,17 +14,94 @@ Via browser
-----------
When a user is logged in, they can easily access the API in their browser, authenticated by their session.
The API endpoint can be found at /library/api/
http://notfellchen.org/
For example: You can check all current adoption notices here: https://notfellchen.org/api/adoption_notice
Via token
---------
.. warning::
This is currently not supported.
All users are able to generate a token that allows them to use the API. This can be done in the user's profile.
An application can then send this token in the request header for authorization.
.. code-block::
$ curl -X GET http://notfellchen.org/api/adoption_notice -H 'Authorization: Token 49b39856955dc6e5cc04365498d4ad30ea3aed78'
.. warning::
Usage or creation of content still has to follow the terms of notfellchen.org.
Copyright of content is often held by rescue organizations, so you are not allowed to simply mirror content.
Talk to the notfellchen team if you want develop such things.
Endpoints
---------
All Endpoints are documented at https://notfellchen.org/api/schema/swagger-ui/ or at https://notfellchen.org/api/schema/redoc/ if you prefer redoc.
The OpenAI schema can be downloaded at https://notfellchen.org/api/schema/
Examples are documented here.
Get Adoption Notices
++++++++++++++++++++
.. code-block::
curl --request GET \
--url https://notfellchen.org/api/adoption_notice \
--header 'Authorization: {{token}}'
Create Adoption Notice
++++++++++++++++++++++
.. code-block::
curl --request POST \
--url https://notfellchen.org/api/adoption_notice \
--header 'Authorization: {{token}}' \
--header 'content-type: multipart/form-data' \
--form name=TestAdoption1 \
--form searching_since=2024-11-19 \
--form 'description=Lorem ipsum **dolor sit** amet' \
--form further_information=https://notfellchen.org \
--form location_string=Berlin \
--form group_only=true
Add Animal to Adoption Notice
+++++++++++++++++++++++++++++
.. code-block::
curl --request POST \
--url https://notfellchen.org/api/animals/ \
--header 'Authorization: {{token}}' \
--header 'content-type: multipart/form-data' \
--form name=TestAnimal1 \
--form date_of_birth=2024-11-19 \
--form 'description=Lorem animal **dolor sit**.' \
--form sex=F \
--form species=1 \
--form adoption_notice=1
Add picture to Animal or Adoption Notice
++++++++++++++++++++++++++++++++++++++++
.. code-block::
curl -X POST https://notfellchen.org/api/images/ \
-H "Authorization: Token {{token}}" \
-F "image=@256-256-crop.jpg" \
-F "alt_text=Puppy enjoying the sunshine" \
-F "attach_to_type=animal" \
-F "attach_to=48
Species
+++++++
Getting available species is mainly important when creating animals
.. code-block::
curl --request GET \
--url https://notfellchen.org/api/species \
--header 'Authorization: {{token}}'
+74
View 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,
}
+1
View File
@@ -67,5 +67,6 @@ Healthchecks
You can configure notfellchen to give a hourly ping to a healthchecks server. If this ping is not received, you will get notified and cna check why the celery jobs are no running.
Add the following to your `notfellchen.cfg` and adjust the URL to match your check.
.. code::
[monitoring]
healthchecks_url=https://health.example.org/ping/5fa7c9b2-753a-4cb3-bcc9-f982f5bc68e8
+5 -7
View File
@@ -16,6 +16,10 @@
# import sys
# sys.path.insert(0, os.path.abspath('.'))
import sys
from pathlib import Path
sys.path.append(str(Path('_ext').resolve()))
# -- Project information -----------------------------------------------------
@@ -28,7 +32,6 @@ version = ''
# The full version, including alpha/beta/rc tags
release = '0.2.0'
# -- General configuration ---------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
@@ -40,6 +43,7 @@ release = '0.2.0'
# ones.
extensions = [
'sphinx.ext.ifconfig',
'drawio'
]
# Add any paths that contain templates here, relative to this directory.
@@ -69,7 +73,6 @@ exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = None
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
@@ -104,7 +107,6 @@ html_static_path = ['_static']
# Output file base name for HTML help builder.
htmlhelp_basename = 'notfellchen'
# -- Options for LaTeX output ------------------------------------------------
latex_elements = {
@@ -133,7 +135,6 @@ latex_documents = [
'Julian-Samuel Gebühr', 'manual'),
]
# -- Options for manual page output ------------------------------------------
# One entry per manual page. List of tuples
@@ -143,7 +144,6 @@ man_pages = [
[author], 1)
]
# -- Options for Texinfo output ----------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
@@ -155,7 +155,6 @@ texinfo_documents = [
'Miscellaneous'),
]
# -- Options for Epub output -------------------------------------------------
# Bibliographic Dublin Core info.
@@ -173,5 +172,4 @@ epub_title = project
# A list of files that should not be packed into the epub file.
epub_exclude_files = ['search.html']
# -- Extension configuration -------------------------------------------------
+2 -2
View File
@@ -5,7 +5,7 @@ Report a bug
^^^^^^^^^^^^
To report a bug, file an issue on `Github
<https://codeberg.org/moanos/notfellchen/issues>`_
<https://github.com/moan0s/notfellchen/issues>`_
Try to include the following information:
@@ -29,7 +29,7 @@ To contribute simply clone the directory, make your changes and file a
pull request.
If you want to know what can be done, have a look at the current `Github
<https://codeberg.org/moanos/notfellchen/issues>`_.
<https://github.com/moan0s/notfellchen/issues>`_.
Get in touch!
^^^^^^^^^^^^^
+3 -4
View File
@@ -5,8 +5,7 @@ What qualifies as release?
^^^^^^^^^^^^^^^^^^^^^^^^^^
A new release should be announced when a significant number functions, bugfixes or other improvements to the software
is made. Usually this indicates a minor release.
Major releases are yet to be determined.
is made. Notfellchen follows `Semantic Versioning <https://semver.org/>`_.
What should be done before a release?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -14,7 +13,7 @@ What should be done before a release?
Tested basic functions
######################
Run :command:`pytest`
Run :command:`nf test src`
Test upgrade on a copy of a production database
###############################################
@@ -38,4 +37,4 @@ Do a final commit on this change, and tag the commit as release with appropriate
git tag -a v1.0.0 -m "Releasing version v1.0.0"
git push origin v1.0.0
Make sure the tag is visible on Codeberg and celebrate 🥳
Make sure the tag is visible on GitHub/Codeberg and celebrate 🥳
Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

File diff suppressed because one or more lines are too long
+16 -3
View File
@@ -6,14 +6,27 @@ Jede Vermittlung kann abonniert werden. Dafür klickst du auf die Glocke neben d
.. image:: abonnieren.png
Einstellungen
-------------
Du kannst E-Mail Benachrichtigungen in den Einstellungen deaktivieren.
.. image::
einstellungen-benachrichtigungen.png
:alt: Screenshot der Profileinstellungen in Notfellchen. Ein roter Pfeil zeigt auf einen Schalter "E-Mail Benachrichtigungen"
Auf der Website
+++++++++++++++
.. image::
screenshot-benachrichtigungen.png
:alt: Screenshot der Menüleiste von Notfellchen.org. Neben dem Symbol einer Glocke steht die Zahl 27.
E-Mail
++++++
Mit während deiner :doc:`registrierung` gibst du eine E-Mail Addresse an.
Benachrichtigungen senden wir per Mail - du kannst das jederzeit in den Einstellungen deaktivieren.
Mit während deiner :doc:`registrierung` gibst du eine E-Mail Adresse an. An diese senden wir Benachrichtigungen, außer
du deaktiviert dies wie oben beschrieben.
Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

+58
View 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.
+10 -4
View File
@@ -1,11 +1,17 @@
******************
User Dokumentation
******************
****************
Benutzerhandbuch
****************
Im Benutzerhandbuch findest du Informationen zur Benutzung von `notfellchen.org <https://notfellchen.org>`_.
Solltest du darüber hinaus Fragen haben, komm gerne auf uns zu: info@notfellchen.org
.. toctree::
:maxdepth: 2
:caption: Inhalt:
erste-schritte.rst
registrierung.rst
vermittlungen.rst
moderationskonzept.rst
benachrichtigungen.rst
organisationen-pruefen.rst
+55
View 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` |
+------------------------------------------------------+---------------+
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@@ -0,0 +1,19 @@
Tierschutzorganisation hinzufügen
=================================
Notfellchen führt eine Liste von Tierheime und anderer Tierschutzorganisationen. Die meisten Tierheime wurden von
OpenStreetMap importiert.
Ausnahmen stellen nicht-ortsgebunden Organisationen wie die Rattenhilfe Süd dar. Sie existieren nicht auf der Karte und
können dort auch nicht sinnvoll verzeichnet werden, daher werden sie nur in Notfellchen geführt.
.. warning::
Generell ist es besser eine Tierschutzorganisation in OpenStreetMap anzulegen, anstatt bei Notfellchen. Das
ermöglichtes anderen die Daten ebenfalls zu nutzen und sie werden ggf. durch die OpenStreetMap Community aktuell gehalten.
Hier erklären wir aber trotzdem wie Tierheime direkt in Notfellchen hinzugefügt werden können, damit es schneller geht
diese Anzulegen und ihnen Vermittlungen zuzuweisen.
Organisationen in Notfellchen hinzufügen
----------------------------------------
+112 -1
View File
@@ -1,7 +1,7 @@
Vermittlungen
=============
Vermittlungen können von allen Nutzer*innen mit Account erstellt werden. Vermittlungen normaler Nutzer*innen kommen dann in eine Warteschlange und werden vom Admin & Modertionsteam geprüft und sichtbar geschaltet.
Vermittlungen können von allen Nutzer\*innen mit Account erstellt werden. Vermittlungen normaler Nutzer*innen kommen dann in eine Warteschlange und werden vom Admin & Modertionsteam geprüft und sichtbar geschaltet.
Tierheime und Pflegestellen können auf Anfrage einen Koordinations-Status bekommen, wodurch sie Vermittlungsanzeigen erstellen können die direkt öffentlich sichtbar sind.
Jede Vermittlung hat ein "Zuletzt-geprüft" Datum, das anzeigt, wann ein Mensch zuletzt überprüft hat, ob die Anzeige noch aktuell ist.
@@ -15,3 +15,114 @@ Die Kommentarfunktion von Vermittlungen ermöglicht es angemeldeten Nutzer*innen
Ersteller*innen von Vermittlungen werden über neue Kommentare per Mail benachrichtigt, ebenso alle die die Vermittlung abonniert haben.
Kommentare können, wie Vermittlungen, gemeldet werden.
.. drawio::
Vermittlung_Lifecycle.drawio.html
Vermittlung-Lifecycle.drawio.png
:alt: Diagramm das den Prozess der Vermittlungen zeigt.
Adoption Notice Status Choices
++++++++++++++++++++++++++++++
Aktiv
-----
Aktive Vermittlungen die über die Suche auffindbar sind.
.. list-table::
:header-rows: 1
:width: 100%
:widths: 1 1 2
* - Value
- Label
- Description
* - ``active_searching``
- Searching
-
* - ``active_interested``
- Interested
- Jemand hat bereits Interesse an den Tieren.
Warte auf Aktion
----------------
Vermittlungen in diesem Status warten darauf, dass ein Mensch sie überprüft. Sie können nicht über die Suche gefunden werden.
.. list-table::
:header-rows: 1
:width: 100%
:widths: 1 1 2
* - ``awaiting_action_waiting_for_review``
- Waiting for review
- Neue Vermittlung die deaktiviert ist bis Moderator*innen sie überprüfen.
* - ``awaiting_action_needs_additional_info``
- Needs additional info
- Deaktiviert bis Informationen nachgetragen werden.
* - ``disabled_unchecked``
- Unchecked
- Vermittlung deaktiviert bis sie vom Team auf Aktualität geprüft wurde.
Geschlossen
-----------
Geschlossene Vermittlungen tauchen in keiner Suche auf. Sie werden aber weiterhin angezeigt, wenn der Link zu ihnen direkt aufgerufen wird.
.. list-table::
:header-rows: 1
:width: 100%
:widths: 1 1 2
* - ``closed_successful_with_notfellchen``
- Successful (with Notfellchen)
- Vermittlung erfolgreich abgeschlossen.
* - ``closed_successful_without_notfellchen``
- Successful (without Notfellchen)
- Vermittlung erfolgreich abgeschlossen.
* - ``closed_animal_died``
- Animal died
- Die zu vermittelnden Tiere sind über die Regenbrücke gegangen.
* - ``closed_for_other_adoption_notice``
- Closed for other adoption notice
- Vermittlung wurde zugunsten einer anderen geschlossen.
* - ``closed_not_open_for_adoption_anymore``
- Not open for adoption anymore
- Tier(e) stehen nicht mehr zur Vermittlung bereit.
* - ``closed_link_to_more_info_not_reachable``
- Der Link zu weiteren Informationen ist nicht mehr erreichbar.
- Der Link zu weiteren Informationen ist nicht mehr erreichbar, die Vermittlung wurde daher automatisch deaktiviert.
* - ``closed_other``
- Other (closed)
- Vermittlung geschlossen.
Deaktiviert
-----------
Deaktivierte Vermittlungen werden nur noch Moderator\*innen und Administrator\*innen angezeigt.
.. list-table::
:header-rows: 1
:width: 100%
:widths: 1 1 2
* - ``disabled_against_the_rules``
- Against the rules
- Vermittlung deaktiviert da sie gegen die Regeln verstößt.
* - ``disabled_other``
- Other (disabled)
- Vermittlung deaktiviert.
+9 -1
View File
@@ -8,6 +8,8 @@ host=localhost
[django]
secret=CHANGE-ME
debug=True
internal_ips=["127.0.0.1"]
cache=False
[database]
backend=sqlite3
@@ -18,10 +20,16 @@ media=./media
static=./static
[mail]
console-only=true
console_only=true
[logging]
app_log_level=INFO
django_log_level=INFO
[geocoding]
api_url=https://photon.hyteck.de/api
api_format=photon
[security]
totp_issuer="NF Localhost"
webauth_allow_insecure_origin=True
+497
View 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
View 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"
}
}
+27 -13
View File
@@ -6,15 +6,15 @@ build-backend = "setuptools.build_meta"
[project]
name = "notfellchen"
description = "A tool to help."
description = "A website to help animals to find a loving home. It features organized input of adoption notices and related animals including automated lifecycle, location-based search, roles, and support for easy checking of rescue organizations."
authors = [
{name = "moanos", email = "julian-samuel@gebuehr.net"},
{ name = "moanos", email = "julian-samuel@gebuehr.net" },
]
maintainers = [
{name = "moanos", email = "julian-samuel@gebuehr.net"},
{ name = "moanos", email = "julian-samuel@gebuehr.net" },
]
keywords = ["animal", "adoption", "django", "rescue", ]
license = {text = "AGPL-3.0-or-later"}
keywords = ["animal", "adoption", "django", "rescue", "rats" ]
license = { text = "AGPL-3.0-or-later" }
classifiers = [
"Environment :: Web",
"License :: OSI Approved :: GNU Affero General Public License v3",
@@ -24,14 +24,10 @@ classifiers = [
]
dependencies = [
"Django",
"coverage",
"codecov",
"sphinx",
"sphinx-rtd-theme",
"gunicorn",
"fontawesomefree",
"whitenoise",
"model_bakery",
"markdown",
"Pillow",
"django-registration",
@@ -39,14 +35,32 @@ dependencies = [
"django-crispy-forms",
"crispy-bootstrap4",
"djangorestframework",
"celery[redis]"
"celery[redis]",
"drf-spectacular[sidecar]",
"django-widget-tweaks",
"django-super-deduper",
"django-allauth[mfa]",
"django_debug_toolbar",
"django-admin-extra-buttons",
"django-simple-history"
]
dynamic = ["version", "readme"]
[project.optional-dependencies]
develop = [
"pytest",
"pytest",
"coverage",
"model_bakery",
]
docs = [
"sphinx",
"sphinx-rtd-theme",
"sphinx-autobuild"
]
shelter-upload = [
"osm2geojson",
"tqdm"
]
[project.urls]
@@ -61,6 +75,6 @@ nf = 'notfellchen.main:main'
[tool.setuptools.dynamic]
version = {attr = "notfellchen.__version__"}
readme = {file = "README.md"}
version = { attr = "notfellchen.__version__" }
readme = { file = "README.md" }
+66
View File
@@ -0,0 +1,66 @@
import argparse
import csv
import os
import requests
OUTPUT_FILE = "export.csv"
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("--instance", type=str, help="API instance URL.")
parser.add_argument("--output-file", type=str, help="Path output 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")
instance = args.instance or os.getenv("NOTFELLCHEN_INSTANCE")
output_file = args.output_file or OUTPUT_FILE
return api_token, instance, output_file
def rat_specific_url_or_none(org):
try:
urls = org["species_specific_urls"]
for url in urls:
# 1 is the key for rats
if url["species"] == 1:
return url["url"]
# Return none if no url for this species is found
return None
except KeyError:
return None
def main():
api_token, instance, output_file = get_config()
# Set headers and endpoint
endpoint = f"{instance}/api/organizations/"
h = {'Authorization': f'Token {api_token}', "content-type": "application/json"}
rescue_orgs_result = requests.get(endpoint, headers=h)
with open(output_file, 'w') as csvfile:
fieldnames = ['id', 'name', 'website', 'rat_specific_website']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
for org in rescue_orgs_result.json():
writer.writerow({'id': org["id"],
'name': org["name"],
'website': org["website"],
"rat_specific_website": rat_specific_url_or_none(org)})
if __name__ == "__main__":
main()
+296
View File
@@ -0,0 +1,296 @@
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}")
def is_not_for_adoption(tierheim):
"""
Returns true if the shelter is not for the purpose of adoption
"""
if tierheim.purpose_adoption == "no":
return True
elif (tierheim.purpose_adoption is None and
(tierheim.purpose_sanctuary == "yes" or tierheim.purpose_release == "yes")):
return True
else:
return False
# 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",
purpose_adoption=get_or_none(tierheim, "animal_shelter:adoption"),
purpose_sanctuary=get_or_none(tierheim, "animal_shelter:sanctuary"),
purpose_release=get_or_none(tierheim, "animal_shelter:release"),
)
# Skip the shelter if it is not for adopting animals
if is_not_for_adoption(th_data):
continue
# 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()
+143 -36
View File
@@ -1,34 +1,64 @@
import csv
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 User, Language, Text, ReportComment, ReportAdoptionNotice, Log, Timestamp
from admin_extra_buttons.api import ExtraButtonsMixin, button, link
from simple_history.admin import SimpleHistoryAdmin
from .models import Language, Text, ReportComment, ReportAdoptionNotice, Log, Timestamp, SearchSubscription, \
SpeciesSpecificURL, ImportantLocation, SocialMediaPost
from .models import Animal, Species, RescueOrganization, AdoptionNotice, Location, Rule, Image, ModerationAction, \
Comment, Report, Announcement, AdoptionNoticeStatus, User, Subscriptions
Comment, Announcement, User, Subscriptions, Notification
from django.utils.translation import gettext_lazy as _
from .tools.model_helpers import AdoptionNoticeStatusChoices
class StatusInline(admin.StackedInline):
model = AdoptionNoticeStatus
def export_to_csv_generic(model, queryset):
meta = 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
@admin.register(AdoptionNotice)
class AdoptionNoticeAdmin(admin.ModelAdmin):
search_fields = ("name__icontains", "description__icontains")
list_filter = ("owner",)
inlines = [
StatusInline,
]
search_fields = ("name__icontains", "description__icontains", "location__icontains")
list_display = ["name", "adoption_notice_status", "owner", "organization", "last_checked_hr"]
list_filter = ("adoption_notice_status", "owner")
actions = ("activate",)
# This admin display is only needed to get a translated label of this property
# If not present the column would show up as "last checked hr"
@admin.display(description=_("zuletzt überprüft"))
def last_checked_hr(self, obj):
return obj.last_checked_hr
def activate(self, request, queryset):
for obj in queryset:
obj.adoption_notice_status = AdoptionNoticeStatusChoices.Active.SEARCHING
obj.save()
activate.short_description = _("Ausgewählte Vermittlungen aktivieren")
# Re-register UserAdmin
@admin.register(User)
class UserAdmin(admin.ModelAdmin):
class UserAdmin(SimpleHistoryAdmin):
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",)
@@ -44,28 +74,19 @@ class UserAdmin(admin.ModelAdmin):
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])
response = export_to_csv_generic(self.model, queryset)
return response
export_as_csv.short_description = _("Ausgewählte User exportieren")
def _reported_content_link(obj):
reported_content = obj.reported_content
return format_html(f'<a href="{reported_content.get_absolute_url}">{reported_content}</a>')
@admin.register(ReportComment)
class ReportCommentAdmin(admin.ModelAdmin):
class ReportCommentAdmin(SimpleHistoryAdmin):
list_display = ["user_comment", "reported_content_link"]
date_hierarchy = "created_at"
@@ -76,7 +97,7 @@ class ReportCommentAdmin(admin.ModelAdmin):
@admin.register(ReportAdoptionNotice)
class ReportAdoptionNoticeAdmin(admin.ModelAdmin):
class ReportAdoptionNoticeAdmin(SimpleHistoryAdmin):
list_display = ["user_comment", "reported_content_link"]
date_hierarchy = "created_at"
@@ -86,30 +107,116 @@ class ReportAdoptionNoticeAdmin(admin.ModelAdmin):
reported_content_link.short_description = "Reported Content"
class SpeciesSpecificURLInline(admin.StackedInline):
model = SpeciesSpecificURL
@admin.register(RescueOrganization)
class RescueOrganizationAdmin(admin.ModelAdmin):
search_fields = ("name__icontains",)
class RescueOrganizationAdmin(SimpleHistoryAdmin):
search_fields = ("name", "description", "internal_comment", "location_string", "location__city")
list_display = ("name", "trusted", "allows_using_materials", "website")
list_filter = ("allows_using_materials", "trusted",)
list_filter = ("allows_using_materials", "trusted", ("external_source_identifier", EmptyFieldListFilter))
inlines = [
SpeciesSpecificURLInline,
]
@admin.register(Text)
class TextAdmin(admin.ModelAdmin):
class TextAdmin(SimpleHistoryAdmin):
search_fields = ("title__icontains", "text_code__icontains",)
@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
class CommentAdmin(SimpleHistoryAdmin):
list_filter = ("user",)
admin.site.register(Animal)
@admin.register(Notification)
class BaseNotificationAdmin(admin.ModelAdmin):
list_filter = ("user_to_notify", "read")
@admin.register(SearchSubscription)
class SearchSubscriptionAdmin(SimpleHistoryAdmin):
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(SimpleHistoryAdmin):
search_fields = ("name__icontains", "city__icontains")
list_display = ("name", "city", "slug")
@admin.display(description=_("Slug"))
def slug(self, obj):
if obj.importantlocation:
return obj.importantlocation.slug
else:
return None
list_filter = [IsImportantListFilter]
inlines = [
ImportantLocationInline,
]
@admin.register(SocialMediaPost)
class SocialMediaPostAdmin(SimpleHistoryAdmin):
list_filter = ("platform",)
@admin.register(Log)
class LogAdmin(ExtraButtonsMixin, admin.ModelAdmin):
ordering = ["-created_at"]
list_filter = ("action",)
list_display = ("action", "user", "created_at")
actions = ("export_as_csv",)
@admin.action(description=_("Ausgewählte Logs exportieren"))
def export_as_csv(self, request, queryset):
response = export_to_csv_generic(Log, queryset)
return response
@button()
def export_all_as_csv(self, request):
actual_queryset = Log.objects.all()
response = export_to_csv_generic(Log, actual_queryset)
return response
@link(href="https://www.google.com/", visible=lambda btn: True)
def invisible(self, button):
button.visible = False
admin.site.register(Animal, SimpleHistoryAdmin)
admin.site.register(Species)
admin.site.register(Location)
admin.site.register(Rule)
admin.site.register(Rule, SimpleHistoryAdmin)
admin.site.register(Image)
admin.site.register(ModerationAction)
admin.site.register(ModerationAction, SimpleHistoryAdmin)
admin.site.register(Language)
admin.site.register(Announcement)
admin.site.register(AdoptionNoticeStatus)
admin.site.register(Subscriptions)
admin.site.register(Log)
admin.site.register(Announcement, SimpleHistoryAdmin)
admin.site.register(Subscriptions, SimpleHistoryAdmin)
admin.site.register(Timestamp)
+33
View 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)
+185 -2
View File
@@ -1,10 +1,193 @@
from ..models import AdoptionNotice
from ..models import Animal, RescueOrganization, AdoptionNotice, Species, Image, Location, SpeciesSpecificURL
from rest_framework import serializers
import math
class ImageSerializer(serializers.ModelSerializer):
width = serializers.SerializerMethodField()
height = serializers.SerializerMethodField()
class Meta:
model = Image
fields = ['id', 'image', 'alt_text', 'width', 'height']
def get_width(self, obj):
return obj.image.width
def get_height(self, obj):
return obj.image.height
class AdoptionNoticeSerializer(serializers.HyperlinkedModelSerializer):
location = serializers.PrimaryKeyRelatedField(
queryset=Location.objects.all(),
required=False,
allow_null=True
)
location_details = serializers.StringRelatedField(source='location', read_only=True)
organization = serializers.PrimaryKeyRelatedField(
queryset=RescueOrganization.objects.all(),
required=False,
allow_null=True
)
organization = serializers.PrimaryKeyRelatedField(
queryset=RescueOrganization.objects.all(),
required=False,
allow_null=True
)
url = serializers.SerializerMethodField()
photos = ImageSerializer(many=True, read_only=True)
def get_url(self, obj):
return obj.get_full_url()
class Meta:
model = AdoptionNotice
fields = ['created_at', 'last_checked', "searching_since", "name", "description", "further_information", "group_only"]
fields = ['created_at', 'last_checked', "searching_since", "name", "description", "further_information",
"group_only", "location", "location_details", "organization", "photos", "adoption_notice_status",
"url"]
class AdoptionNoticeGeoJSONSerializer(serializers.ModelSerializer):
species = serializers.SerializerMethodField()
title = serializers.CharField(source='name')
url = serializers.SerializerMethodField()
location_hr = serializers.SerializerMethodField()
coordinates = serializers.SerializerMethodField()
image_url = serializers.SerializerMethodField()
image_alt = serializers.SerializerMethodField()
class Meta:
model = AdoptionNotice
fields = ('id', 'species', 'title', 'description', 'url', 'location_hr', 'coordinates', 'image_url',
'image_alt')
def get_species(self, obj):
return "rat"
def get_url(self, obj):
return obj.get_absolute_url()
def get_image_url(self, obj):
photo = obj.get_photo()
if photo is not None:
return obj.get_photo().image.url
return None
def get_image_alt(self, obj):
photo = obj.get_photo()
if photo is not None:
return obj.get_photo().alt_text
return None
def get_coordinates(self, obj):
"""
Coordinates are randomly moved around real location, roughly in a circle. The object id is used as angle so that
points are always displayed at the same location (as if they were a seed for a random function).
It's not exactly a circle, because the earth is round.
"""
if obj.location:
longitude_addition = math.sin(obj.id) / 2000
latitude_addition = math.cos(obj.id) / 2000
return [obj.location.longitude + longitude_addition, obj.location.latitude + latitude_addition]
return None
def get_location_hr(self, obj):
if obj.location:
return f"{obj.location}"
return None
class RescueOrgeGeoJSONSerializer(serializers.ModelSerializer):
name = serializers.CharField()
url = serializers.SerializerMethodField()
location_hr = serializers.SerializerMethodField()
coordinates = serializers.SerializerMethodField()
class Meta:
model = AdoptionNotice
fields = ('id', 'name', 'description', 'url', 'location_hr', 'coordinates')
def get_url(self, obj):
return obj.get_absolute_url()
def get_coordinates(self, obj):
"""
Coordinates are randomly moved around real location, roughly in a circle. The object id is used as angle so that
points are always displayed at the same location (as if they were a seed for a random function).
It's not exactly a circle, because the earth is round.
"""
if obj.location:
return [obj.location.longitude, obj.location.latitude]
return None
def get_location_hr(self, obj):
if obj.location.city:
return f"{obj.location.city}"
elif obj.location:
return f"{obj.location}"
return None
class AnimalCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Animal
fields = ["name", "date_of_birth", "description", "species", "sex", "adoption_notice"]
class AnimalGetSerializer(serializers.ModelSerializer):
class Meta:
model = Animal
fields = "__all__"
class SpeciesSpecificURLSerializer(serializers.ModelSerializer):
class Meta:
model = SpeciesSpecificURL
fields = "__all__"
class RescueOrganizationSerializer(serializers.ModelSerializer):
url = serializers.SerializerMethodField()
species_specific_urls = SpeciesSpecificURLSerializer(many=True, read_only=True)
class Meta:
model = RescueOrganization
fields = ('id', 'name', 'url', 'trusted', 'location_string', 'instagram', "facebook", "fediverse_profile",
"email", "phone_number", "website", "updated_at", "created_at", "last_checked", "description",
"external_source_identifier", "external_object_identifier", "exclude_from_check",
"regular_check_status", "ongoing_communication", "twenty_id", "location", "parent_org", "specializations",
"species_specific_urls")
def get_url(self, obj):
return obj.get_absolute_url()
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__"
+16 -2
View File
@@ -1,8 +1,22 @@
from django.urls import path
from .views import (
AdoptionNoticeApiView
AdoptionNoticeApiView,
AnimalApiView, RescueOrganizationApiView, AddImageApiView, SpeciesApiView, LocationApiView,
AdoptionNoticeGeoJSONView, RescueOrgGeoJSONView, AdoptionNoticePerOrgApiView, index
)
urlpatterns = [
path('adoption_notice', AdoptionNoticeApiView.as_view()),
path("", index, name="api-base-url"),
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"),
]
+444 -23
View File
@@ -1,37 +1,458 @@
from django.contrib.auth.models import User
from django.db.models import Q
from django.shortcuts import redirect
from django.urls import reverse
from drf_spectacular.types import OpenApiTypes
from rest_framework.generics import ListAPIView
from fellchensammlung.api.serializers import LocationSerializer, AdoptionNoticeGeoJSONSerializer
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework import permissions
from ..models import AdoptionNotice
from .serializers import AdoptionNoticeSerializer
from django.db import transaction
from fellchensammlung.models import Log, TrustLevel, Location, AdoptionNoticeStatusChoices
from fellchensammlung.tasks import post_adoption_notice_save, post_rescue_org_save
from rest_framework import status, serializers
from rest_framework.permissions import IsAuthenticated
from .renderers import GeoJSONRenderer
from .serializers import (
AnimalGetSerializer,
AnimalCreateSerializer,
RescueOrgeGeoJSONSerializer,
AdoptionNoticeSerializer,
ImageCreateSerializer,
SpeciesSerializer, RescueOrganizationSerializer,
)
from fellchensammlung.models import Animal, RescueOrganization, AdoptionNotice, Species, Image
from drf_spectacular.utils import extend_schema, inline_serializer, OpenApiParameter
class AdoptionNoticeApiView(APIView):
permission_classes = [permissions.IsAuthenticated]
permission_classes = [IsAuthenticated]
@extend_schema(
parameters=[
{
'name': 'id',
'required': False,
'description': 'ID of the adoption notice to retrieve.',
'type': int
},
],
responses={200: AdoptionNoticeSerializer(many=True)}
)
def get(self, request, *args, **kwargs):
serializer_context = {
'request': request,
}
"""
Retrieve adoption notices with their related animals and images.
"""
adoption_notice_id = kwargs.get("id")
if adoption_notice_id:
try:
adoption_notice = AdoptionNotice.objects.get(pk=adoption_notice_id)
serializer = AdoptionNoticeSerializer(adoption_notice, context={"request": request})
return Response(serializer.data, status=status.HTTP_200_OK)
except AdoptionNotice.DoesNotExist:
return Response({"error": "Adoption notice not found."}, status=status.HTTP_404_NOT_FOUND)
adoption_notices = AdoptionNotice.objects.all()
serializer = AdoptionNoticeSerializer(adoption_notices, many=True, context=serializer_context)
serializer = AdoptionNoticeSerializer(adoption_notices, many=True, context={"request": request})
return Response(serializer.data, status=status.HTTP_200_OK)
@transaction.atomic
@extend_schema(
request=AdoptionNoticeSerializer,
responses={201: 'Adoption notice created successfully!'}
)
def post(self, request, *args, **kwargs):
data = {
'name': request.data.get('name'),
"searching_since": request.data.get('searching_since'),
"description": request.data.get('description'),
"organization": request.data.get('organization'),
"further_information": request.data.get('further_information'),
"location_string": request.data.get('location_string'),
"group_only": request.data.get('group_only'),
"owner": request.data.get('owner')
}
serializer = AdoptionNoticeSerializer(data=data)
"""
API view to add an adoption notice.
"""
serializer = AdoptionNoticeSerializer(data=request.data, context={'request': request})
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
adoption_notice = serializer.save(owner=request.user_to_notify)
# Add the location
post_adoption_notice_save.delay_on_commit(adoption_notice.pk)
# Only set active when user has trust level moderator or higher
if request.user_to_notify.trust_level >= TrustLevel.MODERATOR:
adoption_notice.adoption_notice_status = AdoptionNoticeStatusChoices.Active.SEARCHING
else:
adoption_notice.adoption_notice_status = AdoptionNoticeStatusChoices.AwaitingAction.WAITING_FOR_REVIEW
# Log the action
Log.objects.create(
user=request.user_to_notify,
action="add_adoption_notice",
text=f"{request.user_to_notify} added adoption notice {adoption_notice.pk} via API",
)
# Return success response with new adoption notice details
return Response(
{"message": "Adoption notice created successfully!", "id": adoption_notice.pk},
status=status.HTTP_201_CREATED,
)
class AnimalApiView(APIView):
permission_classes = [IsAuthenticated]
@extend_schema(
responses=AnimalGetSerializer
)
def get(self, request, *args, **kwargs):
"""
Get list of animals or a specific animal by ID.
"""
animal_id = kwargs.get("id")
if animal_id:
try:
animal = Animal.objects.get(pk=animal_id)
serializer = AnimalGetSerializer(animal, context={"request": request})
return Response(serializer.data, status=status.HTTP_200_OK)
except Animal.DoesNotExist:
return Response({"error": "Animal not found."}, status=status.HTTP_404_NOT_FOUND)
animals = Animal.objects.all()
serializer = AnimalGetSerializer(animals, many=True, context={"request": request})
return Response(serializer.data, status=status.HTTP_200_OK)
@transaction.atomic
@extend_schema(
request=AnimalCreateSerializer,
responses={201: inline_serializer(
name='Animal',
fields={
'id': serializers.IntegerField(),
"message": serializers.Field()}),
400: "json"}
)
@transaction.atomic
def post(self, request, *args, **kwargs):
"""
Create a new animal.
"""
serializer = AnimalCreateSerializer(data=request.data, context={"request": request})
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
animal = serializer.save(owner=request.user_to_notify)
return Response(
{"message": "Animal created successfully!", "id": animal.id},
status=status.HTTP_201_CREATED,
)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class RescueOrganizationApiView(APIView):
permission_classes = [IsAuthenticated]
@extend_schema(
parameters=[
{
'name': 'id',
'required': False,
'description': 'ID of the rescue organization to retrieve.',
'type': int
},
{
'name': 'trusted',
'required': False,
'description': 'Filter by trusted status (true/false).',
'type': bool
},
{
'name': 'external_object_identifier',
'required': False,
'description': 'Filter by external object identifier. Use "None" to filter for an empty field',
'type': str
},
{
'name': 'external_source_identifier',
'required': False,
'description': 'Filter by external source identifier. Use "None" to filter for an empty field',
'type': str
},
{
'name': 'search',
'required': False,
'description': 'Search by organization name or location name/city.',
'type': str
},
],
responses={200: RescueOrganizationSerializer(many=True)}
)
def get(self, request, *args, **kwargs):
"""
Get list of rescue organizations or a specific organization by ID or get a list with available filters for
- external_object_identifier
- external_source_identifier
"""
org_id = request.query_params.get("id")
external_object_identifier = request.query_params.get("external_object_identifier")
external_source_identifier = request.query_params.get("external_source_identifier")
search_query = request.query_params.get("search")
if org_id:
try:
organization = RescueOrganization.objects.get(pk=org_id)
serializer = RescueOrganizationSerializer(organization, context={"request": request})
return Response(serializer.data, status=status.HTTP_200_OK)
except RescueOrganization.DoesNotExist:
return Response({"error": "Organization not found."}, status=status.HTTP_404_NOT_FOUND)
organizations = RescueOrganization.objects.all()
if external_object_identifier:
if external_object_identifier == "None":
external_object_identifier = None
organizations = organizations.filter(external_object_identifier=external_object_identifier)
if external_source_identifier:
if external_source_identifier == "None":
external_source_identifier = None
organizations = organizations.filter(external_source_identifier=external_source_identifier)
if search_query:
organizations = organizations.filter(
Q(name__icontains=search_query) |
Q(location_string__icontains=search_query) |
Q(location__name__icontains=search_query) |
Q(location__city__icontains=search_query)
)
if organizations.count() == 0:
return Response({"error": "No organizations found."}, status=status.HTTP_404_NOT_FOUND)
serializer = RescueOrganizationSerializer(organizations, many=True, context={"request": request})
return Response(serializer.data, status=status.HTTP_200_OK)
@transaction.atomic
@extend_schema(
request=RescueOrganizationSerializer,
responses={201: 'Rescue organization created successfully!'}
)
def post(self, request, *args, **kwargs):
"""
Create or update a rescue organization.
"""
serializer = RescueOrganizationSerializer(data=request.data, context={"request": request})
if serializer.is_valid():
rescue_org = serializer.save()
if rescue_org.location is None:
# Add the location
post_rescue_org_save.delay_on_commit(rescue_org.pk)
return Response(
{"message": "Rescue organization created successfully!", "id": rescue_org.id},
status=status.HTTP_201_CREATED,
)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@transaction.atomic
@extend_schema(
request=RescueOrganizationSerializer,
responses={200: 'Rescue organization updated successfully!'}
)
def patch(self, request, *args, **kwargs):
"""
Partially update a rescue organization.
"""
org_id = request.data.get("id")
if not org_id:
return Response({"error": "ID is required for updating."}, status=status.HTTP_400_BAD_REQUEST)
try:
organization = RescueOrganization.objects.get(pk=org_id)
except RescueOrganization.DoesNotExist:
return Response({"error": "Organization not found."}, status=status.HTTP_404_NOT_FOUND)
serializer = RescueOrganizationSerializer(organization, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response({"message": "Rescue organization updated successfully!"}, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class AddImageApiView(APIView):
permission_classes = [IsAuthenticated]
@transaction.atomic
@extend_schema(
request=ImageCreateSerializer,
responses={201: 'Image added successfully!'}
)
def post(self, request, *args, **kwargs):
"""
Add an image to an animal or adoption notice.
"""
serializer = ImageCreateSerializer(data=request.data, context={"request": request})
if serializer.is_valid():
if serializer.validated_data["attach_to_type"] == "animal":
object_to_attach_to = Animal.objects.get(id=serializer.validated_data["attach_to"])
elif serializer.validated_data["attach_to_type"] == "adoption_notice":
object_to_attach_to = AdoptionNotice.objects.get(id=serializer.validated_data["attach_to"])
else:
raise ValueError("Unknown attach_to_type given, should not happen. Check serializer")
serializer.validated_data.pop('attach_to_type', None)
serializer.validated_data.pop('attach_to', None)
image = serializer.save(owner=request.user_to_notify)
object_to_attach_to.photos.add(image)
return Response(
{"message": "Image added successfully!", "id": image.id},
status=status.HTTP_201_CREATED,
)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class SpeciesApiView(APIView):
permission_classes = [IsAuthenticated]
@extend_schema(
responses={200: SpeciesSerializer(many=True)}
)
def get(self, request, *args, **kwargs):
"""
Retrieve a list of species.
"""
species = Species.objects.all()
serializer = SpeciesSerializer(species, many=True, context={"request": request})
return Response(serializer.data, status=status.HTTP_200_OK)
class LocationApiView(APIView):
permission_classes = [IsAuthenticated]
@extend_schema(
parameters=[
{
'name': 'id',
'required': False,
'description': 'ID of the location to retrieve.',
'type': int
},
],
responses={200: LocationSerializer(many=True)}
)
def get(self, request, *args, **kwargs):
"""
Retrieve a location
"""
location_id = kwargs.get("id")
if location_id:
try:
location = Location.objects.get(pk=location_id)
serializer = LocationSerializer(location, context={"request": request})
return Response(serializer.data, status=status.HTTP_200_OK)
except Location.DoesNotExist:
return Response({"error": "Location not found."}, status=status.HTTP_404_NOT_FOUND)
locations = Location.objects.all()
serializer = LocationSerializer(locations, many=True, context={"request": request})
return Response(serializer.data, status=status.HTTP_200_OK)
@transaction.atomic
@extend_schema(
request=LocationSerializer,
responses={201: 'Location created successfully!'}
)
def post(self, request, *args, **kwargs):
"""
API view to add a location
"""
serializer = LocationSerializer(data=request.data, context={'request': request})
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
location = serializer.save()
# Log the action
Log.objects.create(
user=request.user,
action="add_location",
text=f"{request.user} added adoption notice {location.pk} via API",
)
# Return success response with new adoption notice details
return Response(
{"message": "Location created successfully!", "id": location.pk},
status=status.HTTP_201_CREATED,
)
class AdoptionNoticeGeoJSONView(ListAPIView):
queryset = AdoptionNotice.objects.select_related('location').filter(location__isnull=False).filter(
adoption_notice_status__in=AdoptionNoticeStatusChoices.Active.values)
serializer_class = AdoptionNoticeGeoJSONSerializer
renderer_classes = [GeoJSONRenderer]
class RescueOrgGeoJSONView(ListAPIView):
queryset = RescueOrganization.objects.select_related('location').filter(location__isnull=False)
serializer_class = RescueOrgeGeoJSONSerializer
renderer_classes = [GeoJSONRenderer]
class AdoptionNoticePerOrgApiView(APIView):
permission_classes = [IsAuthenticated]
@extend_schema(
parameters=[
OpenApiParameter(
name='id',
required=False,
description='ID of the rescue organization from which to retrieve adoption notices.',
type=OpenApiTypes.INT
),
OpenApiParameter(
name='in_hierarchy',
type=OpenApiTypes.BOOL,
required=False,
description='Show all Adoption Notices in hierarchy.',
),
OpenApiParameter(
name='status',
type=OpenApiTypes.STR,
required=False,
description='Show all Adoption Notices in a certain status. Comma separated list of values e.g. '
'"active,closed"',
),
],
responses={200: AdoptionNoticeSerializer(many=True)}
)
def get(self, request, *args, **kwargs):
"""
Retrieve adoption notices with their related animals and images.
"""
org_id = kwargs.get("id")
in_hierarchy = request.query_params.get("in_hierarchy")
an_status = request.query_params.get("status")
try:
org = RescueOrganization.objects.get(id=org_id)
except RescueOrganization.DoesNotExist:
return Response({"error": "Rescue Organization notice not found."}, status=status.HTTP_404_NOT_FOUND)
if in_hierarchy:
adoption_notices = org.adoption_notices_in_hierarchy
else:
adoption_notices = AdoptionNotice.objects.filter(organization=org)
if an_status:
status_list = an_status.lower().strip().split(",")
temporary_an_storage = []
if "active" in status_list:
active_ans = [adoption_notice for adoption_notice in adoption_notices if
adoption_notice.adoption_notice_status in AdoptionNoticeStatusChoices.Active.values]
temporary_an_storage.extend(active_ans)
if "closed" in status_list:
closed_ans = [adoption_notice for adoption_notice in adoption_notices if
adoption_notice.adoption_notice_status in AdoptionNoticeStatusChoices.Closed.values]
temporary_an_storage.extend(closed_ans)
if "disabled" in status_list:
disabled_ans = [adoption_notice for adoption_notice in adoption_notices if
adoption_notice.adoption_notice_status in AdoptionNoticeStatusChoices.Disabled.values]
temporary_an_storage.extend(disabled_ans)
if "awaiting_action" in status_list:
awaiting_action_ans = [adoption_notice for adoption_notice in adoption_notices if
adoption_notice.adoption_notice_status in AdoptionNoticeStatusChoices.AwaitingAction.values]
temporary_an_storage.extend(awaiting_action_ans)
adoption_notices = temporary_an_storage
serializer = AdoptionNoticeSerializer(adoption_notices, many=True, context={"request": request})
return Response(serializer.data, status=status.HTTP_200_OK)
def index(request):
return redirect(reverse("swagger-ui"))
@@ -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
View 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
View 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"),
]
+123 -102
View File
@@ -1,18 +1,21 @@
from django import forms
from django.forms.widgets import Textarea
from .models import AdoptionNotice, Animal, Image, ReportAdoptionNotice, ReportComment, ModerationAction, User, Species, \
Comment, SexChoicesWithAll
Comment, SexChoicesWithAll, DistanceChoices, SpeciesSpecificURL, RescueOrganization
from django_registration.forms import RegistrationForm
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit, Layout, Fieldset, HTML, Row, Column, Field, Hidden
from crispy_forms.layout import Submit, Layout, Fieldset
from django.utils.translation import gettext_lazy as _
from notfellchen.settings import MEDIA_URL
from crispy_forms.layout import Div
from .tools.model_helpers import reason_for_signup_label, reason_for_signup_help_text, AdoptionNoticeStatusChoices
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"]
"dog", "rabbit", "fox", "fancy rat", "meerschweinchen"]
if value not in animal_list:
raise forms.ValidationError(_("Dieses Tier kenne ich nicht. Probier ein anderes"))
@@ -22,95 +25,46 @@ class DateInput(forms.DateInput):
class AdoptionNoticeForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
if 'in_adoption_notice_creation_flow' in kwargs:
in_flow = kwargs.pop('in_adoption_notice_creation_flow')
else:
in_flow = False
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_id = 'form-adoption-notice'
self.helper.form_class = 'card'
self.helper.form_method = 'post'
if in_flow:
submit = Submit('save-and-add-another-animal', _('Speichern'))
else:
submit = Submit('submit', _('Speichern'))
self.helper.layout = Layout(
Fieldset(
_('Vermittlungsdetails'),
'name',
'species',
'num_animals',
'date_of_birth',
'sex',
'group_only',
'searching_since',
'location_string',
'organization',
'description',
'further_information',
),
submit)
template_name = "fellchensammlung/forms/form_snippets.html"
class Meta:
model = AdoptionNotice
fields = ['name', "group_only", "further_information", "description", "searching_since", "location_string",
"organization"]
class AdoptionNoticeFormWithDateWidget(AdoptionNoticeForm):
class Meta:
model = AdoptionNotice
fields = ['name', "group_only", "further_information", "description", "searching_since", "location_string",
"organization"]
widgets = {
'searching_since': DateInput(),
'searching_since': DateInput(format=('%Y-%m-%d')),
}
class AnimalForm(forms.ModelForm):
class AdoptionNoticeFormAutoAnimal(AdoptionNoticeForm):
def __init__(self, *args, **kwargs):
if 'in_adoption_notice_creation_flow' in kwargs:
adding = kwargs.pop('in_adoption_notice_creation_flow')
else:
adding = False
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_class = 'form-animal card'
if adding:
self.helper.add_input(Submit('save-and-add-another-animal', _('Speichern und weiteres Tier hinzufügen')))
self.helper.add_input(Submit('save-and-finish', _('Speichern und beenden')))
else:
self.helper.add_input(Submit('submit', _('Speichern'), css_class="btn"))
class Meta:
model = Animal
fields = ["name", "date_of_birth", "species", "sex", "description"]
class AnimalFormWithDateWidget(AnimalForm):
class Meta:
model = Animal
fields = ["name", "date_of_birth", "species", "sex", "description"]
widgets = {
'date_of_birth': DateInput(),
}
class AdoptionNoticeFormWithDateWidgetAutoAnimal(AdoptionNoticeFormWithDateWidget):
def __init__(self, *args, **kwargs):
super(AdoptionNoticeFormWithDateWidgetAutoAnimal, self).__init__(*args, **kwargs)
super(AdoptionNoticeFormAutoAnimal, self).__init__(*args, **kwargs)
self.fields["num_animals"] = forms.fields.IntegerField(min_value=1, max_value=30, label=_("Zahl der Tiere"))
animal_form = AnimalForm()
self.fields["species"] = animal_form.fields["species"]
self.fields["sex"] = animal_form.fields["sex"]
self.fields["date_of_birth"] = animal_form.fields["date_of_birth"]
self.fields["date_of_birth"].widget = DateInput()
self.fields["date_of_birth"].widget = DateInput(format=('%Y-%m-%d'))
class AnimalForm(forms.ModelForm):
template_name = "fellchensammlung/forms/form_snippets.html"
class Meta:
model = Animal
fields = ["name", "date_of_birth", "species", "sex", "description"]
widgets = {
'date_of_birth': DateInput(format=('%Y-%m-%d'))
}
class UpdateRescueOrgRegularCheckStatus(forms.ModelForm):
template_name = "fellchensammlung/forms/form_snippets.html"
class Meta:
model = RescueOrganization
fields = ["regular_check_status"]
class ImageForm(forms.ModelForm):
@@ -124,11 +78,21 @@ class ImageForm(forms.ModelForm):
self.helper.form_id = 'form-animal-photo'
self.helper.form_class = 'card'
self.helper.form_method = 'post'
if in_flow:
self.helper.add_input(Submit('save-and-add-another', _('Speichern und weiteres Foto hinzufügen')))
self.helper.add_input(Submit('submit', _('Speichern')))
submits = Div(Submit('submit', _('Speichern')),
Submit('save-and-add-another', _('Speichern und weiteres Foto hinzufügen')),
css_class="container-edit-buttons")
else:
self.helper.add_input(Submit('submit', _('Submit')))
submits = Fieldset(Submit('submit', _('Speichern')), css_class="container-edit-buttons")
self.helper.layout = Layout(
Div(
'image',
'alt_text',
css_class="spaced",
),
submits
)
class Meta:
model = Image
@@ -136,57 +100,114 @@ class ImageForm(forms.ModelForm):
class ReportAdoptionNoticeForm(forms.ModelForm):
template_name = "fellchensammlung/forms/form_snippets.html"
class Meta:
model = ReportAdoptionNotice
fields = ('reported_broken_rules', 'user_comment')
class ReportCommentForm(forms.ModelForm):
template_name = "fellchensammlung/forms/form_snippets.html"
class Meta:
model = ReportComment
fields = ('reported_broken_rules', 'user_comment')
class CommentForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_class = 'form-comments'
self.helper.add_input(Hidden('action', 'comment'))
self.helper.add_input(Submit('submit', _('Kommentieren'), css_class="btn2"))
class UserModCommentForm(forms.ModelForm):
class Meta:
model = User
fields = ('mod_notes',)
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('text',)
class SpeciesURLForm(forms.ModelForm):
class Meta:
model = SpeciesSpecificURL
fields = ('species', 'url')
class RescueOrgInternalComment(forms.ModelForm):
class Meta:
model = RescueOrganization
fields = ('internal_comment',)
class ModerationActionForm(forms.ModelForm):
class Meta:
model = ModerationAction
fields = ('action', 'public_comment', 'private_comment')
class AddedRegistrationForm(forms.Form):
reason_for_signup = forms.CharField(label=reason_for_signup_label,
help_text=reason_for_signup_help_text,
widget=Textarea)
captcha = forms.CharField(validators=[animal_validator], label=_("Nenne eine bekannte Tierart"), help_text=_(
"Bitte nenne hier eine bekannte Tierart (z.B. ein Tier das an der Leine geführt wird). Das Fragen wir dich um "
"sicherzustellen, dass du kein Roboter bist."))
def signup(self, request, user):
pass
class CustomRegistrationForm(RegistrationForm):
class Meta(RegistrationForm.Meta):
model = User
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."))
template_name = "fellchensammlung/forms/form_snippets.html"
def __init__(self, *args, **kwargs):
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"))
def _get_distances():
return {i: i for i in [20, 50, 100, 200, 500]}
captcha = forms.CharField(validators=[animal_validator], label=_("Nenne eine bekannte Tierart"), help_text=_(
"Bitte nenne hier eine bekannte Tierart (z.B. ein Tier das an der Leine geführt wird). Das Fragen wir dich um sicherzustellen, dass du kein Roboter bist."))
class AdoptionNoticeSearchForm(forms.Form):
location = forms.CharField(max_length=20, label=_("Stadt"), required=False)
max_distance = forms.ChoiceField(choices=_get_distances, label=_("Max. Distanz"))
template_name = "fellchensammlung/forms/form_snippets.html"
sex = forms.ChoiceField(choices=SexChoicesWithAll, label=_("Geschlecht"), required=False,
initial=SexChoicesWithAll.ALL)
max_distance = forms.ChoiceField(choices=DistanceChoices, initial=DistanceChoices.ONE_HUNDRED,
label=_("Suchradius"))
location_string = forms.CharField(max_length=100, label=_("Stadt"), required=False)
class RescueOrgSearchForm(forms.Form):
template_name = "fellchensammlung/forms/form_snippets.html"
location_string = forms.CharField(max_length=100, label=_("Stadt"), required=False)
max_distance = forms.ChoiceField(choices=DistanceChoices, initial=DistanceChoices.TWENTY,
label=_("Suchradius"))
class RescueOrgSearchByNameForm(forms.Form):
template_name = "fellchensammlung/forms/form_snippets.html"
name = forms.CharField(max_length=100, label=_("Name der Organisation"), required=False)
class CloseAdoptionNoticeForm(forms.ModelForm):
template_name = "fellchensammlung/forms/form_snippets.html"
adoption_notice_status = forms.ChoiceField(choices=AdoptionNoticeStatusChoices.Closed,
label=_("Status"),
help_text=_("Gib den neuen Status der Vermittlung an"))
class Meta:
model = AdoptionNotice
fields = ('adoption_notice_status',)
class RescueOrgForm(forms.ModelForm):
template_name = "fellchensammlung/forms/form_snippets.html"
class Meta:
model = RescueOrganization
fields = ('name', 'allows_using_materials', 'location_string', "email", "phone_number", "website", "instagram",
"facebook", "fediverse_profile", "internal_comment", "description", "external_source_identifier",
"external_object_identifier",
"parent_org")
+30 -30
View File
@@ -1,43 +1,43 @@
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.conf import settings
from django.core import mail
from fellchensammlung.models import User, CommentNotification, BaseNotification, TrustLevel
from notfellchen.settings import host
NEWLINE = "\r\n"
from fellchensammlung.models import User, Notification, TrustLevel, NotificationTypeChoices
from fellchensammlung.tools.model_helpers import ndm
def mail_admins_new_report(report):
subject = _("Neue Meldung")
def notify_mods_new_report(report, notification_type):
"""
Sends an e-mail to all users that should handle the report.
"""
for moderator in User.objects.filter(trust_level__gt=TrustLevel.MODERATOR):
greeting = _("Moin,") + "{NEWLINE}"
new_report_text = _("es wurde ein Regelverstoß gemeldet.") + "{NEWLINE}"
if len(report.reported_broken_rules.all()) > 0:
reported_rules_text = (f"Ein Verstoß gegen die folgenden Regeln wurde gemeldet:{NEWLINE}"
f"- {f'{NEWLINE} - '.join([str(r) for r in report.reported_broken_rules.all()])}{NEWLINE}")
if notification_type == NotificationTypeChoices.NEW_REPORT_AN:
title = _("Vermittlung gemeldet")
elif notification_type == NotificationTypeChoices.NEW_REPORT_COMMENT:
title = _("Kommentar gemeldet")
else:
reported_rules_text = f"Es wurden keine Regeln angegeben gegen die Verstoßen wurde.{NEWLINE}"
if report.user_comment:
comment_text = f'Kommentar zum Report: "{report.user_comment}"{NEWLINE}'
else:
comment_text = f"Es wurde kein Kommentar hinzugefügt.{NEWLINE}"
report_url = "https://" + host + report.get_absolute_url()
link_text = f"Um alle Details zu sehen, geh bitte auf: {report_url}"
body_text = greeting + new_report_text + reported_rules_text + comment_text + link_text
message = mail.EmailMessage(subject, body_text, settings.DEFAULT_FROM_EMAIL, [moderator.email])
message.send()
raise NotImplementedError
notification = Notification.objects.create(
notification_type=notification_type,
user_to_notify=moderator,
report=report,
title=title,
)
notification.save()
def send_notification_email(notification_pk):
try:
notification = CommentNotification.objects.get(pk=notification_pk)
except CommentNotification.DoesNotExist:
notification = BaseNotification.objects.get(pk=notification_pk)
subject = f"🔔 {notification.title}"
body_text = notification.text
message = mail.EmailMessage(subject, body_text, settings.DEFAULT_FROM_EMAIL, [notification.user.email])
message.send()
notification = Notification.objects.get(pk=notification_pk)
subject = f"{notification.title}"
context = {"notification": notification, }
html_message = render_to_string(ndm[notification.notification_type].email_html_template, context)
plain_message = render_to_string(ndm[notification.notification_type].email_plain_template, context)
mail.send_mail(subject, plain_message, settings.DEFAULT_FROM_EMAIL,
[notification.user_to_notify.email],
html_message=html_message)
@@ -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()
@@ -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)
@@ -0,0 +1,13 @@
from django.core.management import BaseCommand
from notfellchen import settings
class Command(BaseCommand):
help = 'Print the current settings'
def handle(self, *args, **options):
for key in settings.__dir__():
if key.startswith("_") or key == "SECRET_KEY":
continue
print(f"{key} = {getattr(settings, key)}")
@@ -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,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',),
),
]
@@ -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'),
),
]
@@ -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')},
),
]
@@ -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),
),
]
@@ -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',
),
]
@@ -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'),
),
]
@@ -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'),
),
]
@@ -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),
]
@@ -0,0 +1,18 @@
# Generated by Django 5.2.1 on 2025-09-06 13:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fellchensammlung', '0066_add_slug_to_species'),
]
operations = [
migrations.AlterField(
model_name='species',
name='slug',
field=models.SlugField(unique=True, verbose_name='Slug'),
),
]
@@ -0,0 +1,23 @@
# Generated by Django 5.2.1 on 2025-09-29 15:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fellchensammlung', '0067_alter_species_slug'),
]
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'), ('awaiting_action_unchecked', 'Unchecked'), ('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_other', 'Other (disabled)')], max_length=64, verbose_name='Status'),
),
migrations.AlterField(
model_name='image',
name='image',
field=models.ImageField(help_text='Wähle ein Bild aus', upload_to='images', verbose_name='Bild'),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 5.2.1 on 2025-10-20 08:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fellchensammlung', '0068_alter_adoptionnotice_adoption_notice_status_and_more'),
]
operations = [
migrations.AddField(
model_name='rescueorganization',
name='regular_check_status',
field=models.CharField(choices=[('regular_check', 'Wird regelmäßig geprüft'), ('excluded_no_online_listing', 'Exkludiert: Tiere werden nicht online gelistet'), ('excluded_other_org', 'Exkludiert: Andere Organisation wird geprüft'), ('excluded_scope', 'Exkludiert: Organisation hat nie Notfellchen-relevanten Vermittlungen'), ('excluded_other', 'Exkludiert: Anderer Grund')], default='regular_check', help_text='Organisationen können, durch ändern dieser Einstellung, von der regelmäßigen Prüfung ausgeschlossen werden.', max_length=30, verbose_name='Status der regelmäßigen Prüfung'),
),
]
@@ -0,0 +1,23 @@
# Generated by Django 5.2.8 on 2025-11-16 16:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fellchensammlung', '0069_rescueorganization_regular_check_status'),
]
operations = [
migrations.AddField(
model_name='user',
name='mod_notes',
field=models.TextField(blank=True, null=True, verbose_name='Moderationsnotizen'),
),
migrations.AlterField(
model_name='user',
name='reason_for_signup',
field=models.TextField(help_text="Wir würden gerne wissen warum du dich registrierst, 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'),
),
]
@@ -0,0 +1,367 @@
# Generated by Django 5.2.8 on 2025-11-16 17:37
import django.contrib.auth.validators
import django.db.models.deletion
import django.utils.timezone
import simple_history.models
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fellchensammlung', '0070_user_mod_notes_alter_user_reason_for_signup'),
]
operations = [
migrations.CreateModel(
name='HistoricalAdoptionNotice',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('created_at', models.DateField(default=django.utils.timezone.now, verbose_name='Erstellt am')),
('updated_at', models.DateTimeField(blank=True, editable=False)),
('last_checked', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Zuletzt überprüft am')),
('searching_since', models.DateField(verbose_name='Sucht nach einem Zuhause seit')),
('name', models.CharField(max_length=200, verbose_name='Titel der Vermittlung')),
('description', models.TextField(blank=True, null=True, verbose_name='Beschreibung')),
('further_information', 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')),
('group_only', models.BooleanField(default=False, verbose_name='Ausschließlich Gruppenadoption')),
('location_string', models.CharField(max_length=200, verbose_name='Ortsangabe')),
('adoption_notice_status', 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'), ('awaiting_action_unchecked', 'Unchecked'), ('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_other', 'Other (disabled)')], max_length=64, verbose_name='Status')),
('adoption_process', models.TextField(blank=True, choices=[('contact_person_in_an', 'Kontaktiere die Person im Vermittlungstext')], max_length=64, null=True, verbose_name='Adoptionsprozess')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('location', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.location')),
('organization', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.rescueorganization', verbose_name='Organisation')),
('owner', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Creator')),
],
options={
'verbose_name': 'historical Vermittlung',
'verbose_name_plural': 'historical Vermittlungen',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalAnimal',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('date_of_birth', models.DateField(verbose_name='Geburtsdatum')),
('name', models.CharField(max_length=200, verbose_name='Name')),
('description', models.TextField(blank=True, null=True, verbose_name='Beschreibung')),
('sex', 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')),
('updated_at', models.DateTimeField(blank=True, editable=False)),
('created_at', models.DateTimeField(blank=True, editable=False)),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('adoption_notice', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.adoptionnotice')),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('owner', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL)),
('species', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.species', verbose_name='Tierart')),
],
options={
'verbose_name': 'historical Tier',
'verbose_name_plural': 'historical Tiere',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalAnnouncement',
fields=[
('text_ptr', models.ForeignKey(auto_created=True, blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, parent_link=True, related_name='+', to='fellchensammlung.text')),
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('title', models.CharField(max_length=100, verbose_name='Titel')),
('content', models.TextField(verbose_name='Inhalt')),
('text_code', models.CharField(blank=True, max_length=24, verbose_name='Text code')),
('logged_in_only', models.BooleanField(default=False)),
('created_at', models.DateTimeField(blank=True, editable=False)),
('updated_at', models.DateTimeField(blank=True, editable=False)),
('publish_start_time', models.DateTimeField(verbose_name='Veröffentlichungszeitpunkt')),
('publish_end_time', models.DateTimeField(verbose_name='Veröffentlichungsende')),
('type', models.CharField(choices=[('important', 'important'), ('warning', 'warning'), ('info', 'info')], default='info', max_length=100)),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('language', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.language', verbose_name='Sprache')),
],
options={
'verbose_name': 'historical Banner',
'verbose_name_plural': 'historical Banner',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalComment',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('created_at', models.DateTimeField(blank=True, editable=False)),
('updated_at', models.DateTimeField(blank=True, editable=False)),
('text', models.TextField(verbose_name='Inhalt')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('adoption_notice', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.adoptionnotice', verbose_name='Vermittlung')),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('reply_to', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.comment', verbose_name='Antwort auf')),
('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Nutzer*in')),
],
options={
'verbose_name': 'historical Kommentar',
'verbose_name_plural': 'historical Kommentare',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalModerationAction',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('action', models.CharField(choices=[('user_banned', 'User was banned'), ('content_deleted', 'Content was deleted'), ('comment', 'Comment was added'), ('other_action_taken', 'Other action was taken'), ('no_action_taken', 'No action was taken')], max_length=30)),
('created_at', models.DateTimeField(blank=True, editable=False)),
('updated_at', models.DateTimeField(blank=True, editable=False)),
('public_comment', models.TextField(blank=True)),
('private_comment', models.TextField(blank=True)),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('report', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.report')),
],
options={
'verbose_name': 'historical Moderationsaktion',
'verbose_name_plural': 'historical Moderationsaktionen',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalReport',
fields=[
('id', models.UUIDField(db_index=True, default=uuid.uuid4, help_text='ID dieses reports', verbose_name='ID')),
('status', models.CharField(choices=[('action taken', 'Action was taken'), ('no action taken', 'No action was taken'), ('waiting', 'Waiting for moderator action')], max_length=30)),
('user_comment', models.TextField(blank=True, verbose_name='Kommentar/Zusätzliche Information')),
('updated_at', models.DateTimeField(blank=True, editable=False, verbose_name='Zuletzt geändert am')),
('created_at', models.DateTimeField(blank=True, editable=False, verbose_name='Erstellt am')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'historical Meldung',
'verbose_name_plural': 'historical Meldungen',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalRescueOrganization',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('name', models.CharField(max_length=200)),
('trusted', models.BooleanField(default=False, verbose_name='Vertrauenswürdig')),
('allows_using_materials', 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')),
('location_string', models.CharField(blank=True, max_length=200, null=True, verbose_name='Ort der Organisation')),
('instagram', models.URLField(blank=True, null=True, verbose_name='Instagram Profil')),
('facebook', models.URLField(blank=True, null=True, verbose_name='Facebook Profil')),
('fediverse_profile', models.URLField(blank=True, null=True, verbose_name='Fediverse Profil')),
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='E-Mail')),
('phone_number', models.CharField(blank=True, max_length=15, null=True, verbose_name='Telefonnummer')),
('website', models.URLField(blank=True, null=True, verbose_name='Website')),
('updated_at', models.DateTimeField(blank=True, editable=False)),
('created_at', models.DateTimeField(blank=True, editable=False)),
('last_checked', models.DateTimeField(blank=True, editable=False, verbose_name='Datum der letzten Prüfung')),
('internal_comment', models.TextField(blank=True, null=True, verbose_name='Interner Kommentar')),
('description', models.TextField(blank=True, null=True, verbose_name='Beschreibung')),
('external_object_identifier', models.CharField(blank=True, max_length=200, null=True, verbose_name='External Object Identifier')),
('external_source_identifier', models.CharField(blank=True, choices=[('OSM', 'Open Street Map')], max_length=200, null=True, verbose_name='External Source Identifier')),
('exclude_from_check', 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')),
('regular_check_status', models.CharField(choices=[('regular_check', 'Wird regelmäßig geprüft'), ('excluded_no_online_listing', 'Exkludiert: Tiere werden nicht online gelistet'), ('excluded_other_org', 'Exkludiert: Andere Organisation wird geprüft'), ('excluded_scope', 'Exkludiert: Organisation hat nie Notfellchen-relevanten Vermittlungen'), ('excluded_other', 'Exkludiert: Anderer Grund')], default='regular_check', help_text='Organisationen können, durch ändern dieser Einstellung, von der regelmäßigen Prüfung ausgeschlossen werden.', max_length=30, verbose_name='Status der regelmäßigen Prüfung')),
('ongoing_communication', models.BooleanField(default=False, help_text='Es findet gerade Kommunikation zwischen Notfellchen und der Organisation statt.', verbose_name='In aktiver Kommunikation')),
('twenty_id', models.UUIDField(blank=True, help_text='ID der der Organisation in Twenty', null=True, verbose_name='Twenty-ID')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('location', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.location')),
('parent_org', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.rescueorganization')),
],
options={
'verbose_name': 'historical Tierschutzorganisation',
'verbose_name_plural': 'historical Tierschutzorganisationen',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalRule',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('title', models.CharField(max_length=200)),
('rule_text', models.TextField(verbose_name='Regeltext')),
('rule_identifier', 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')),
('updated_at', models.DateTimeField(blank=True, editable=False, verbose_name='Zuletzt geändert am')),
('created_at', models.DateTimeField(blank=True, editable=False, verbose_name='Erstellt am')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('language', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.language', verbose_name='Sprache')),
],
options={
'verbose_name': 'historical Regel',
'verbose_name_plural': 'historical Regeln',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalSearchSubscription',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('sex', 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')),
('max_distance', models.IntegerField(choices=[(20, '20 km'), (50, '50 km'), (100, '100 km'), (200, '200 km'), (500, '500 km')], null=True)),
('updated_at', models.DateTimeField(blank=True, editable=False, verbose_name='Zuletzt geändert am')),
('created_at', models.DateTimeField(blank=True, editable=False, verbose_name='Erstellt am')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('location', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.location')),
('owner', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'historical Abonnierte Suche',
'verbose_name_plural': 'historical Abonnierte Suchen',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalSocialMediaPost',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, 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')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('adoption_notice', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.adoptionnotice', verbose_name='Vermittlung')),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'historical social media post',
'verbose_name_plural': 'historical social media posts',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalSubscriptions',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('created_at', models.DateTimeField(blank=True, editable=False)),
('updated_at', models.DateTimeField(blank=True, editable=False)),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('adoption_notice', models.ForeignKey(blank=True, db_constraint=False, help_text='Vermittlung die abonniert wurde', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.adoptionnotice', verbose_name='Vermittlung')),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('owner', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Nutzer*in')),
],
options={
'verbose_name': 'historical Abonnement',
'verbose_name_plural': 'historical Abonnements',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalText',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('title', models.CharField(max_length=100, verbose_name='Titel')),
('content', models.TextField(verbose_name='Inhalt')),
('text_code', models.CharField(blank=True, max_length=24, verbose_name='Text code')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('language', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.language', verbose_name='Sprache')),
],
options={
'verbose_name': 'historical Text',
'verbose_name_plural': 'historical Texte',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalUser',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(db_index=True, error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('trust_level', models.IntegerField(choices=[(1, 'Member'), (2, 'Coordinator'), (3, 'Moderator'), (4, 'Admin')], default=1)),
('updated_at', models.DateTimeField(blank=True, editable=False)),
('reason_for_signup', models.TextField(help_text="Wir würden gerne wissen warum du dich registrierst, 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')),
('mod_notes', models.TextField(blank=True, null=True, verbose_name='Moderationsnotizen')),
('email_notifications', models.BooleanField(default=True, verbose_name='Benachrichtigung per E-Mail')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('organization_affiliation', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.rescueorganization', verbose_name='Organisation')),
('preferred_language', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.language', verbose_name='Bevorzugte Sprache')),
],
options={
'verbose_name': 'historical Nutzer*in',
'verbose_name_plural': 'historical Nutzer*innen',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
]
@@ -0,0 +1,23 @@
# Generated by Django 5.2.8 on 2025-11-29 09:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fellchensammlung', '0071_historicaladoptionnotice_historicalanimal_and_more'),
]
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'), ('awaiting_action_unchecked', 'Unchecked'), ('closed_successfully', 'Erfolgreich vermittelt'), ('closed_animal_died', 'Tier gestorben'), ('closed_for_other_adoption_notice', 'Vermittlung wurde zugunsten einer anderen geschlossen.'), ('closed_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.'), ('closed_other', 'Anderes'), ('disabled_against_the_rules', 'Against the rules'), ('disabled_other', 'Other (disabled)')], max_length=64, verbose_name='Status'),
),
migrations.AlterField(
model_name='historicaladoptionnotice',
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'), ('awaiting_action_unchecked', 'Unchecked'), ('closed_successfully', 'Erfolgreich vermittelt'), ('closed_animal_died', 'Tier gestorben'), ('closed_for_other_adoption_notice', 'Vermittlung wurde zugunsten einer anderen geschlossen.'), ('closed_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.'), ('closed_other', 'Anderes'), ('disabled_against_the_rules', 'Against the rules'), ('disabled_other', 'Other (disabled)')], max_length=64, verbose_name='Status'),
),
]
@@ -0,0 +1,23 @@
import logging
from django.db import migrations
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.
AdoptionNotice = apps.get_model("fellchensammlung", "AdoptionNotice")
for adoption_notice in AdoptionNotice.objects.filter(
adoption_notice_status__in=("closed_successful_without_notfellchen", "closed_successful_with_notfellchen")):
adoption_notice.adoption_notice_status = "closed_successfully"
adoption_notice.save()
class Migration(migrations.Migration):
dependencies = [
('fellchensammlung', '0072_alter_adoptionnotice_adoption_notice_status_and_more'),
]
operations = [
migrations.RunPython(migrate_status),
]
@@ -0,0 +1,104 @@
# Generated by Django 5.2.8 on 2026-01-20 21:09
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("fellchensammlung", "0073_adoption_notice_status_successful"),
]
operations = [
migrations.AlterField(
model_name="animal",
name="date_of_birth",
field=models.DateField(blank=True, null=True, verbose_name="Geburtsdatum"),
),
migrations.AlterField(
model_name="historicalanimal",
name="date_of_birth",
field=models.DateField(blank=True, null=True, verbose_name="Geburtsdatum"),
),
migrations.AlterField(
model_name="historicalrescueorganization",
name="external_object_identifier",
field=models.CharField(
blank=True,
help_text="Id des Objekts in der externen Datenbank (kann leer gelassen werden)",
max_length=200,
null=True,
verbose_name="External Object Identifier",
),
),
migrations.AlterField(
model_name="historicalrescueorganization",
name="external_source_identifier",
field=models.CharField(
blank=True,
choices=[("OSM", "Open Street Map")],
help_text="Name der Datenbank aus der die Tierschutzorganisation importiert wurde (kann leer gelassen werden)",
max_length=200,
null=True,
verbose_name="External Source Identifier",
),
),
migrations.AlterField(
model_name="historicalrescueorganization",
name="parent_org",
field=models.ForeignKey(
blank=True,
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
to="fellchensammlung.rescueorganization",
verbose_name="Übergeordnete Organisation",
),
),
migrations.AlterField(
model_name="rescueorganization",
name="external_object_identifier",
field=models.CharField(
blank=True,
help_text="Id des Objekts in der externen Datenbank (kann leer gelassen werden)",
max_length=200,
null=True,
verbose_name="External Object Identifier",
),
),
migrations.AlterField(
model_name="rescueorganization",
name="external_source_identifier",
field=models.CharField(
blank=True,
choices=[("OSM", "Open Street Map")],
help_text="Name der Datenbank aus der die Tierschutzorganisation importiert wurde (kann leer gelassen werden)",
max_length=200,
null=True,
verbose_name="External Source Identifier",
),
),
migrations.AlterField(
model_name="rescueorganization",
name="parent_org",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="fellchensammlung.rescueorganization",
verbose_name="Übergeordnete Organisation",
),
),
migrations.AlterField(
model_name="speciesspecificurl",
name="rescue_organization",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="species_specific_urls",
to="fellchensammlung.rescueorganization",
verbose_name="Tierschutzorganisation",
),
),
]
File diff suppressed because it is too large Load Diff

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