Compare commits
41 Commits
151ce0d88e
...
ef824d6b82
| Author | SHA1 | Date | |
|---|---|---|---|
| ef824d6b82 | |||
| 153dc8c048 | |||
| 00e32e0f7c | |||
| 75789f4247 | |||
| 96221a1232 | |||
| c230020fc8 | |||
| e5c822dbb4 | |||
| 9457b19e83 | |||
| 0e6a9c7c7d | |||
| c324394949 | |||
| dfa44ea0c6 | |||
| c309ea9ed4 | |||
| f3f7131912 | |||
| 9a3cbffa42 | |||
| f2bad5f171 | |||
| 515d9453d1 | |||
| d7797ab02e | |||
| ad3511c086 | |||
| f9f35f9104 | |||
| c4da3318c2 | |||
| c529153373 | |||
| 8d0e4c62f7 | |||
| 4ab546bd8e | |||
| cab9010aff | |||
| 97df7e3ed6 | |||
| e64cc4bd5f | |||
| a498971d66 | |||
| 527ab07b6f | |||
| e2e236d650 | |||
| c4100a9ade | |||
| 9511b46af0 | |||
| 5b906a7708 | |||
| d68e836b57 | |||
| fe77f1da8d | |||
| 78b71690c0 | |||
| 3b9ee95abc | |||
| b4e50364de | |||
| b014b3b227 | |||
| 99bfe460ee | |||
| d4c7caa42d | |||
| 32c8fc88cf |
@@ -9,15 +9,14 @@ RUN apt install gettext -y
|
|||||||
RUN apt install libpq-dev gcc -y
|
RUN apt install libpq-dev gcc -y
|
||||||
COPY . /app
|
COPY . /app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN mkdir /app/data
|
RUN mkdir /app/data/static -p
|
||||||
RUN mkdir /app/data/static
|
|
||||||
RUN mkdir /app/data/media
|
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 collectstatic --noinput
|
||||||
RUN nf compilemessages --ignore venv
|
RUN nf compilemessages --ignore venv
|
||||||
|
|
||||||
COPY docker/notfellchen.bash /bin/notfellchen
|
COPY docker/entrypoint.sh /bin/notfellchen
|
||||||
|
|
||||||
EXPOSE 7345
|
EXPOSE 7345
|
||||||
CMD ["notfellchen"]
|
CMD ["notfellchen"]
|
||||||
|
|||||||
19
docs/user/tierschutzorganisation-hinzufuegen.rst
Normal file
19
docs/user/tierschutzorganisation-hinzufuegen.rst
Normal file
@@ -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
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
@@ -9,6 +9,7 @@ host=localhost
|
|||||||
secret=CHANGE-ME
|
secret=CHANGE-ME
|
||||||
debug=True
|
debug=True
|
||||||
internal_ips=["127.0.0.1"]
|
internal_ips=["127.0.0.1"]
|
||||||
|
cache=False
|
||||||
|
|
||||||
[database]
|
[database]
|
||||||
backend=sqlite3
|
backend=sqlite3
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ dependencies = [
|
|||||||
"django-widget-tweaks",
|
"django-widget-tweaks",
|
||||||
"django-super-deduper",
|
"django-super-deduper",
|
||||||
"django-allauth[mfa]",
|
"django-allauth[mfa]",
|
||||||
|
"django_debug_toolbar",
|
||||||
|
"django-admin-extra-buttons",
|
||||||
|
"django-simple-history"
|
||||||
]
|
]
|
||||||
|
|
||||||
dynamic = ["version", "readme"]
|
dynamic = ["version", "readme"]
|
||||||
@@ -49,13 +52,16 @@ develop = [
|
|||||||
"pytest",
|
"pytest",
|
||||||
"coverage",
|
"coverage",
|
||||||
"model_bakery",
|
"model_bakery",
|
||||||
"debug_toolbar",
|
|
||||||
]
|
]
|
||||||
docs = [
|
docs = [
|
||||||
"sphinx",
|
"sphinx",
|
||||||
"sphinx-rtd-theme",
|
"sphinx-rtd-theme",
|
||||||
"sphinx-autobuild"
|
"sphinx-autobuild"
|
||||||
]
|
]
|
||||||
|
shelter-upload = [
|
||||||
|
"osm2geojson",
|
||||||
|
"tqdm"
|
||||||
|
]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
homepage = "https://notfellchen.org"
|
homepage = "https://notfellchen.org"
|
||||||
|
|||||||
66
scripts/extract_rescue_org_websites.py
Normal file
66
scripts/extract_rescue_org_websites.py
Normal 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()
|
||||||
@@ -133,6 +133,19 @@ def get_center_coordinates(geometry):
|
|||||||
raise ValueError(f"Unsupported geometry type: {geom_type}")
|
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
|
# 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):
|
def get_overpass_result(area, data_file):
|
||||||
"""Build the Overpass query for fetching animal shelters in the specified area."""
|
"""Build the Overpass query for fetching animal shelters in the specified area."""
|
||||||
@@ -225,7 +238,13 @@ def main():
|
|||||||
description=get_or_none(tierheim, "opening_hours"),
|
description=get_or_none(tierheim, "opening_hours"),
|
||||||
external_object_identifier=tierheim["id"],
|
external_object_identifier=tierheim["id"],
|
||||||
EXTERNAL_SOURCE_IDENTIFIER="OSM",
|
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
|
# Define here for later
|
||||||
optional_data = ["email", "phone_number", "website", "description", "fediverse_profile", "facebook",
|
optional_data = ["email", "phone_number", "website", "description", "fediverse_profile", "facebook",
|
||||||
@@ -250,7 +269,8 @@ def main():
|
|||||||
|
|
||||||
result = requests.patch(endpoint, json=org_patch_data, headers=h)
|
result = requests.patch(endpoint, json=org_patch_data, headers=h)
|
||||||
if result.status_code != 200:
|
if result.status_code != 200:
|
||||||
logging.warning(f"Updating {tierheim['properties']['name']} failed:{result.status_code} {result.json()}")
|
logging.warning(
|
||||||
|
f"Updating {tierheim['properties']['name']} failed:{result.status_code} {result.json()}")
|
||||||
continue
|
continue
|
||||||
# Rescue organization does not exist
|
# Rescue organization does not exist
|
||||||
else:
|
else:
|
||||||
@@ -268,7 +288,8 @@ def main():
|
|||||||
|
|
||||||
if result.status_code != 201:
|
if result.status_code != 201:
|
||||||
print(f"{idx} {tierheim["properties"]["name"]}:{result.status_code} {result.json()}")
|
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.")
|
print(
|
||||||
|
f"Upload finished. Inserted {stats['num_inserted_orgs']} new orgs and updated {stats['num_updated_orgs']} orgs.")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ from django.utils.html import format_html
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.http import urlencode
|
from django.utils.http import urlencode
|
||||||
|
|
||||||
|
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, \
|
from .models import Language, Text, ReportComment, ReportAdoptionNotice, Log, Timestamp, SearchSubscription, \
|
||||||
SpeciesSpecificURL, ImportantLocation, SocialMediaPost
|
SpeciesSpecificURL, ImportantLocation, SocialMediaPost
|
||||||
|
|
||||||
@@ -17,12 +20,34 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from .tools.model_helpers import AdoptionNoticeStatusChoices
|
from .tools.model_helpers import AdoptionNoticeStatusChoices
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
@admin.register(AdoptionNotice)
|
||||||
class AdoptionNoticeAdmin(admin.ModelAdmin):
|
class AdoptionNoticeAdmin(admin.ModelAdmin):
|
||||||
search_fields = ("name__icontains", "description__icontains")
|
search_fields = ("name__icontains", "description__icontains", "location__icontains")
|
||||||
list_filter = ("owner",)
|
list_display = ["name", "adoption_notice_status", "owner", "organization", "last_checked_hr"]
|
||||||
|
list_filter = ("adoption_notice_status", "owner")
|
||||||
actions = ("activate",)
|
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):
|
def activate(self, request, queryset):
|
||||||
for obj in queryset:
|
for obj in queryset:
|
||||||
obj.adoption_notice_status = AdoptionNoticeStatusChoices.Active.SEARCHING
|
obj.adoption_notice_status = AdoptionNoticeStatusChoices.Active.SEARCHING
|
||||||
@@ -33,7 +58,7 @@ class AdoptionNoticeAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
# Re-register UserAdmin
|
# Re-register UserAdmin
|
||||||
@admin.register(User)
|
@admin.register(User)
|
||||||
class UserAdmin(admin.ModelAdmin):
|
class UserAdmin(SimpleHistoryAdmin):
|
||||||
search_fields = ("usernamname__icontains", "first_name__icontains", "last_name__icontains", "email__icontains")
|
search_fields = ("usernamname__icontains", "first_name__icontains", "last_name__icontains", "email__icontains")
|
||||||
list_display = ("username", "email", "trust_level", "is_active", "view_adoption_notices")
|
list_display = ("username", "email", "trust_level", "is_active", "view_adoption_notices")
|
||||||
list_filter = ("is_active", "trust_level",)
|
list_filter = ("is_active", "trust_level",)
|
||||||
@@ -49,17 +74,7 @@ class UserAdmin(admin.ModelAdmin):
|
|||||||
return format_html('<a href="{}">{} Adoption Notices</a>', url, count)
|
return format_html('<a href="{}">{} Adoption Notices</a>', url, count)
|
||||||
|
|
||||||
def export_as_csv(self, request, queryset):
|
def export_as_csv(self, request, queryset):
|
||||||
meta = self.model._meta
|
response = export_to_csv_generic(self.model, queryset)
|
||||||
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
|
return response
|
||||||
|
|
||||||
export_as_csv.short_description = _("Ausgewählte User exportieren")
|
export_as_csv.short_description = _("Ausgewählte User exportieren")
|
||||||
@@ -71,7 +86,7 @@ def _reported_content_link(obj):
|
|||||||
|
|
||||||
|
|
||||||
@admin.register(ReportComment)
|
@admin.register(ReportComment)
|
||||||
class ReportCommentAdmin(admin.ModelAdmin):
|
class ReportCommentAdmin(SimpleHistoryAdmin):
|
||||||
list_display = ["user_comment", "reported_content_link"]
|
list_display = ["user_comment", "reported_content_link"]
|
||||||
date_hierarchy = "created_at"
|
date_hierarchy = "created_at"
|
||||||
|
|
||||||
@@ -82,7 +97,7 @@ class ReportCommentAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
|
|
||||||
@admin.register(ReportAdoptionNotice)
|
@admin.register(ReportAdoptionNotice)
|
||||||
class ReportAdoptionNoticeAdmin(admin.ModelAdmin):
|
class ReportAdoptionNoticeAdmin(SimpleHistoryAdmin):
|
||||||
list_display = ["user_comment", "reported_content_link"]
|
list_display = ["user_comment", "reported_content_link"]
|
||||||
date_hierarchy = "created_at"
|
date_hierarchy = "created_at"
|
||||||
|
|
||||||
@@ -97,7 +112,7 @@ class SpeciesSpecificURLInline(admin.StackedInline):
|
|||||||
|
|
||||||
|
|
||||||
@admin.register(RescueOrganization)
|
@admin.register(RescueOrganization)
|
||||||
class RescueOrganizationAdmin(admin.ModelAdmin):
|
class RescueOrganizationAdmin(SimpleHistoryAdmin):
|
||||||
search_fields = ("name", "description", "internal_comment", "location_string", "location__city")
|
search_fields = ("name", "description", "internal_comment", "location_string", "location__city")
|
||||||
list_display = ("name", "trusted", "allows_using_materials", "website")
|
list_display = ("name", "trusted", "allows_using_materials", "website")
|
||||||
list_filter = ("allows_using_materials", "trusted", ("external_source_identifier", EmptyFieldListFilter))
|
list_filter = ("allows_using_materials", "trusted", ("external_source_identifier", EmptyFieldListFilter))
|
||||||
@@ -108,12 +123,12 @@ class RescueOrganizationAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
|
|
||||||
@admin.register(Text)
|
@admin.register(Text)
|
||||||
class TextAdmin(admin.ModelAdmin):
|
class TextAdmin(SimpleHistoryAdmin):
|
||||||
search_fields = ("title__icontains", "text_code__icontains",)
|
search_fields = ("title__icontains", "text_code__icontains",)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Comment)
|
@admin.register(Comment)
|
||||||
class CommentAdmin(admin.ModelAdmin):
|
class CommentAdmin(SimpleHistoryAdmin):
|
||||||
list_filter = ("user",)
|
list_filter = ("user",)
|
||||||
|
|
||||||
|
|
||||||
@@ -123,7 +138,7 @@ class BaseNotificationAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
|
|
||||||
@admin.register(SearchSubscription)
|
@admin.register(SearchSubscription)
|
||||||
class SearchSubscriptionAdmin(admin.ModelAdmin):
|
class SearchSubscriptionAdmin(SimpleHistoryAdmin):
|
||||||
list_filter = ("owner",)
|
list_filter = ("owner",)
|
||||||
|
|
||||||
|
|
||||||
@@ -151,8 +166,17 @@ class IsImportantListFilter(admin.SimpleListFilter):
|
|||||||
|
|
||||||
|
|
||||||
@admin.register(Location)
|
@admin.register(Location)
|
||||||
class LocationAdmin(admin.ModelAdmin):
|
class LocationAdmin(SimpleHistoryAdmin):
|
||||||
search_fields = ("name__icontains", "city__icontains")
|
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]
|
list_filter = [IsImportantListFilter]
|
||||||
inlines = [
|
inlines = [
|
||||||
ImportantLocationInline,
|
ImportantLocationInline,
|
||||||
@@ -160,23 +184,39 @@ class LocationAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
|
|
||||||
@admin.register(SocialMediaPost)
|
@admin.register(SocialMediaPost)
|
||||||
class SocialMediaPostAdmin(admin.ModelAdmin):
|
class SocialMediaPostAdmin(SimpleHistoryAdmin):
|
||||||
list_filter = ("platform",)
|
list_filter = ("platform",)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Log)
|
@admin.register(Log)
|
||||||
class LogAdmin(admin.ModelAdmin):
|
class LogAdmin(ExtraButtonsMixin, admin.ModelAdmin):
|
||||||
ordering = ["-created_at"]
|
ordering = ["-created_at"]
|
||||||
list_filter = ("action",)
|
list_filter = ("action",)
|
||||||
list_display = ("action", "user", "created_at")
|
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)
|
admin.site.register(Animal, SimpleHistoryAdmin)
|
||||||
admin.site.register(Species)
|
admin.site.register(Species)
|
||||||
admin.site.register(Rule)
|
admin.site.register(Rule, SimpleHistoryAdmin)
|
||||||
admin.site.register(Image)
|
admin.site.register(Image)
|
||||||
admin.site.register(ModerationAction)
|
admin.site.register(ModerationAction, SimpleHistoryAdmin)
|
||||||
admin.site.register(Language)
|
admin.site.register(Language)
|
||||||
admin.site.register(Announcement)
|
admin.site.register(Announcement, SimpleHistoryAdmin)
|
||||||
admin.site.register(Subscriptions)
|
admin.site.register(Subscriptions, SimpleHistoryAdmin)
|
||||||
admin.site.register(Timestamp)
|
admin.site.register(Timestamp)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from ..models import Animal, RescueOrganization, AdoptionNotice, Species, Image, Location
|
from ..models import Animal, RescueOrganization, AdoptionNotice, Species, Image, Location, SpeciesSpecificURL
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
import math
|
import math
|
||||||
|
|
||||||
@@ -144,10 +144,26 @@ class AnimalGetSerializer(serializers.ModelSerializer):
|
|||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class SpeciesSpecificURLSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = SpeciesSpecificURL
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
class RescueOrganizationSerializer(serializers.ModelSerializer):
|
class RescueOrganizationSerializer(serializers.ModelSerializer):
|
||||||
|
url = serializers.SerializerMethodField()
|
||||||
|
species_specific_urls = SpeciesSpecificURLSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RescueOrganization
|
model = RescueOrganization
|
||||||
exclude = ["internal_comment", "allows_using_materials"]
|
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):
|
class ImageCreateSerializer(serializers.ModelSerializer):
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ from django.urls import path
|
|||||||
from .views import (
|
from .views import (
|
||||||
AdoptionNoticeApiView,
|
AdoptionNoticeApiView,
|
||||||
AnimalApiView, RescueOrganizationApiView, AddImageApiView, SpeciesApiView, LocationApiView,
|
AnimalApiView, RescueOrganizationApiView, AddImageApiView, SpeciesApiView, LocationApiView,
|
||||||
AdoptionNoticeGeoJSONView, RescueOrgGeoJSONView, AdoptionNoticePerOrgApiView
|
AdoptionNoticeGeoJSONView, RescueOrgGeoJSONView, AdoptionNoticePerOrgApiView, index
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
path("", index, name="api-base-url"),
|
||||||
path("adoption_notice", AdoptionNoticeApiView.as_view(), name="api-adoption-notice-list"),
|
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.geojson", AdoptionNoticeGeoJSONView.as_view(), name="api-adoption-notice-list-geojson"),
|
||||||
path("adoption_notice/<int:id>/", AdoptionNoticeApiView.as_view(), name="api-adoption-notice-detail"),
|
path("adoption_notice/<int:id>/", AdoptionNoticeApiView.as_view(), name="api-adoption-notice-detail"),
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.urls import reverse
|
||||||
from drf_spectacular.types import OpenApiTypes
|
from drf_spectacular.types import OpenApiTypes
|
||||||
from rest_framework.generics import ListAPIView
|
from rest_framework.generics import ListAPIView
|
||||||
|
|
||||||
@@ -450,3 +452,7 @@ class AdoptionNoticePerOrgApiView(APIView):
|
|||||||
adoption_notices = temporary_an_storage
|
adoption_notices = temporary_an_storage
|
||||||
serializer = AdoptionNoticeSerializer(adoption_notices, many=True, context={"request": request})
|
serializer = AdoptionNoticeSerializer(adoption_notices, many=True, context={"request": request})
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
def index(request):
|
||||||
|
return redirect(reverse("swagger-ui"))
|
||||||
|
|||||||
@@ -5,18 +5,17 @@ from .models import AdoptionNotice, Animal, Image, ReportAdoptionNotice, ReportC
|
|||||||
Comment, SexChoicesWithAll, DistanceChoices, SpeciesSpecificURL, RescueOrganization
|
Comment, SexChoicesWithAll, DistanceChoices, SpeciesSpecificURL, RescueOrganization
|
||||||
from django_registration.forms import RegistrationForm
|
from django_registration.forms import RegistrationForm
|
||||||
from crispy_forms.helper import FormHelper
|
from crispy_forms.helper import FormHelper
|
||||||
from crispy_forms.layout import Submit, Layout, Fieldset, HTML, Row, Column, Field, Hidden
|
from crispy_forms.layout import Submit, Layout, Fieldset
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from notfellchen.settings import MEDIA_URL
|
|
||||||
from crispy_forms.layout import Div
|
from crispy_forms.layout import Div
|
||||||
|
|
||||||
from .tools.model_helpers import reason_for_signup_label, reason_for_signup_help_text
|
from .tools.model_helpers import reason_for_signup_label, reason_for_signup_help_text, AdoptionNoticeStatusChoices
|
||||||
|
|
||||||
|
|
||||||
def animal_validator(value: str):
|
def animal_validator(value: str):
|
||||||
value = value.lower()
|
value = value.lower()
|
||||||
animal_list = ["ratte", "farbratte", "katze", "hund", "kaninchen", "hase", "kuh", "fuchs", "cow", "rat", "cat",
|
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:
|
if value not in animal_list:
|
||||||
raise forms.ValidationError(_("Dieses Tier kenne ich nicht. Probier ein anderes"))
|
raise forms.ValidationError(_("Dieses Tier kenne ich nicht. Probier ein anderes"))
|
||||||
|
|
||||||
@@ -116,6 +115,12 @@ class ReportCommentForm(forms.ModelForm):
|
|||||||
fields = ('reported_broken_rules', 'user_comment')
|
fields = ('reported_broken_rules', 'user_comment')
|
||||||
|
|
||||||
|
|
||||||
|
class UserModCommentForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = ('mod_notes',)
|
||||||
|
|
||||||
|
|
||||||
class CommentForm(forms.ModelForm):
|
class CommentForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Comment
|
model = Comment
|
||||||
@@ -178,3 +183,31 @@ class RescueOrgSearchForm(forms.Form):
|
|||||||
location_string = forms.CharField(max_length=100, label=_("Stadt"), required=False)
|
location_string = forms.CharField(max_length=100, label=_("Stadt"), required=False)
|
||||||
max_distance = forms.ChoiceField(choices=DistanceChoices, initial=DistanceChoices.TWENTY,
|
max_distance = forms.ChoiceField(choices=DistanceChoices, initial=DistanceChoices.TWENTY,
|
||||||
label=_("Suchradius"))
|
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")
|
||||||
|
|||||||
13
src/fellchensammlung/management/commands/print-settings.py
Normal file
13
src/fellchensammlung/management/commands/print-settings.py
Normal file
@@ -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,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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -7,6 +7,7 @@ from django.contrib.auth.models import Group
|
|||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
import base64
|
import base64
|
||||||
|
from simple_history.models import HistoricalRecords
|
||||||
|
|
||||||
from .tools import misc, geo
|
from .tools import misc, geo
|
||||||
from notfellchen.settings import MEDIA_URL, base_url
|
from notfellchen.settings import MEDIA_URL, base_url
|
||||||
@@ -121,11 +122,11 @@ class ExternalSourceChoices(models.TextChoices):
|
|||||||
|
|
||||||
|
|
||||||
class AllowUseOfMaterialsChices(models.TextChoices):
|
class AllowUseOfMaterialsChices(models.TextChoices):
|
||||||
USE_MATERIALS_ALLOWED = "allowed", _("Usage allowed")
|
USE_MATERIALS_ALLOWED = "allowed", _("Nutzung erlaubt")
|
||||||
USE_MATERIALS_REQUESTED = "requested", _("Usage requested")
|
USE_MATERIALS_REQUESTED = "requested", _("Nutzung angefragt")
|
||||||
USE_MATERIALS_DENIED = "denied", _("Usage denied")
|
USE_MATERIALS_DENIED = "denied", _("Nutzung verweigert")
|
||||||
USE_MATERIALS_OTHER = "other", _("It's complicated")
|
USE_MATERIALS_OTHER = "other", _("Es ist kompliziert")
|
||||||
USE_MATERIALS_NOT_ASKED = "not_asked", _("Not asked")
|
USE_MATERIALS_NOT_ASKED = "not_asked", _("Nutzung noch nicht angefragt")
|
||||||
|
|
||||||
|
|
||||||
class Species(models.Model):
|
class Species(models.Model):
|
||||||
@@ -166,10 +167,14 @@ class RescueOrganization(models.Model):
|
|||||||
internal_comment = models.TextField(verbose_name=_("Interner Kommentar"), null=True, blank=True, )
|
internal_comment = models.TextField(verbose_name=_("Interner Kommentar"), null=True, blank=True, )
|
||||||
description = models.TextField(null=True, blank=True, verbose_name=_('Beschreibung')) # Markdown allowed
|
description = models.TextField(null=True, blank=True, verbose_name=_('Beschreibung')) # Markdown allowed
|
||||||
external_object_identifier = models.CharField(max_length=200, null=True, blank=True,
|
external_object_identifier = models.CharField(max_length=200, null=True, blank=True,
|
||||||
verbose_name=_('External Object Identifier'))
|
verbose_name=_('External Object Identifier'),
|
||||||
|
help_text=_(
|
||||||
|
"Id des Objekts in der externen Datenbank (kann leer gelassen werden)"))
|
||||||
external_source_identifier = models.CharField(max_length=200, null=True, blank=True,
|
external_source_identifier = models.CharField(max_length=200, null=True, blank=True,
|
||||||
choices=ExternalSourceChoices.choices,
|
choices=ExternalSourceChoices.choices,
|
||||||
verbose_name=_('External Source Identifier'))
|
verbose_name=_('External Source Identifier'),
|
||||||
|
help_text=_(
|
||||||
|
"Name der Datenbank aus der die Tierschutzorganisation importiert wurde (kann leer gelassen werden)"))
|
||||||
exclude_from_check = models.BooleanField(default=False, verbose_name=_('Von Prüfung ausschließen'),
|
exclude_from_check = models.BooleanField(default=False, verbose_name=_('Von Prüfung ausschließen'),
|
||||||
help_text=_("Organisation von der manuellen Überprüfung ausschließen, "
|
help_text=_("Organisation von der manuellen Überprüfung ausschließen, "
|
||||||
"z.B. weil Tiere nicht online geführt werden"))
|
"z.B. weil Tiere nicht online geführt werden"))
|
||||||
@@ -182,11 +187,13 @@ class RescueOrganization(models.Model):
|
|||||||
ongoing_communication = models.BooleanField(default=False, verbose_name=_('In aktiver Kommunikation'),
|
ongoing_communication = models.BooleanField(default=False, verbose_name=_('In aktiver Kommunikation'),
|
||||||
help_text=_(
|
help_text=_(
|
||||||
"Es findet gerade Kommunikation zwischen Notfellchen und der Organisation statt."))
|
"Es findet gerade Kommunikation zwischen Notfellchen und der Organisation statt."))
|
||||||
parent_org = models.ForeignKey("RescueOrganization", on_delete=models.PROTECT, blank=True, null=True)
|
parent_org = models.ForeignKey("RescueOrganization", on_delete=models.PROTECT, blank=True, null=True,
|
||||||
|
verbose_name=_("Übergeordnete Organisation"))
|
||||||
# allows to specify if a rescue organization has a specialization for dedicated species
|
# allows to specify if a rescue organization has a specialization for dedicated species
|
||||||
specializations = models.ManyToManyField(Species, blank=True)
|
specializations = models.ManyToManyField(Species, blank=True)
|
||||||
twenty_id = models.UUIDField(verbose_name=_("Twenty-ID"), null=True, blank=True,
|
twenty_id = models.UUIDField(verbose_name=_("Twenty-ID"), null=True, blank=True,
|
||||||
help_text=_("ID der der Organisation in Twenty"))
|
help_text=_("ID der der Organisation in Twenty"))
|
||||||
|
history = HistoricalRecords()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('external_object_identifier', 'external_source_identifier',)
|
unique_together = ('external_object_identifier', 'external_source_identifier',)
|
||||||
@@ -250,6 +257,7 @@ class RescueOrganization(models.Model):
|
|||||||
|
|
||||||
def set_checked(self):
|
def set_checked(self):
|
||||||
self.last_checked = timezone.now()
|
self.last_checked = timezone.now()
|
||||||
|
self._change_reason = 'Organization checked'
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -309,7 +317,9 @@ class User(AbstractUser):
|
|||||||
organization_affiliation = models.ForeignKey(RescueOrganization, on_delete=models.PROTECT, null=True, blank=True,
|
organization_affiliation = models.ForeignKey(RescueOrganization, on_delete=models.PROTECT, null=True, blank=True,
|
||||||
verbose_name=_('Organisation'))
|
verbose_name=_('Organisation'))
|
||||||
reason_for_signup = models.TextField(verbose_name=reason_for_signup_label, help_text=reason_for_signup_help_text)
|
reason_for_signup = models.TextField(verbose_name=reason_for_signup_label, help_text=reason_for_signup_help_text)
|
||||||
|
mod_notes = models.TextField(verbose_name=_("Moderationsnotizen"), null=True, blank=True)
|
||||||
email_notifications = models.BooleanField(verbose_name=_("Benachrichtigung per E-Mail"), default=True)
|
email_notifications = models.BooleanField(verbose_name=_("Benachrichtigung per E-Mail"), default=True)
|
||||||
|
history = HistoricalRecords()
|
||||||
REQUIRED_FIELDS = ["reason_for_signup", "email"]
|
REQUIRED_FIELDS = ["reason_for_signup", "email"]
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -405,6 +415,7 @@ class AdoptionNotice(models.Model):
|
|||||||
adoption_process = models.TextField(null=True, blank=True,
|
adoption_process = models.TextField(null=True, blank=True,
|
||||||
max_length=64, verbose_name=_('Adoptionsprozess'),
|
max_length=64, verbose_name=_('Adoptionsprozess'),
|
||||||
choices=AdoptionProcess)
|
choices=AdoptionProcess)
|
||||||
|
history = HistoricalRecords()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def animals(self):
|
def animals(self):
|
||||||
@@ -419,7 +430,6 @@ class AdoptionNotice(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def num_per_sex(self):
|
def num_per_sex(self):
|
||||||
print(f"{self.pk} x")
|
|
||||||
num_per_sex = dict()
|
num_per_sex = dict()
|
||||||
for sex in SexChoices:
|
for sex in SexChoices:
|
||||||
num_per_sex[sex] = len([animal for animal in self.animals if animal.sex == sex])
|
num_per_sex[sex] = len([animal for animal in self.animals if animal.sex == sex])
|
||||||
@@ -542,6 +552,13 @@ class AdoptionNotice(models.Model):
|
|||||||
def _values_of(list_of_enums):
|
def _values_of(list_of_enums):
|
||||||
return list(map(lambda x: x[0], list_of_enums))
|
return list(map(lambda x: x[0], list_of_enums))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status_category(self):
|
||||||
|
ansc = AdoptionNoticeStatusChoices
|
||||||
|
for status_category in [ansc.Active, ansc.Disabled, ansc.Closed, ansc.AwaitingAction]:
|
||||||
|
if self.adoption_notice_status in self._values_of(status_category.choices):
|
||||||
|
return status_category.__name__.lower()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_active(self):
|
def is_active(self):
|
||||||
return self.adoption_notice_status in self._values_of(AdoptionNoticeStatusChoices.Active.choices)
|
return self.adoption_notice_status in self._values_of(AdoptionNoticeStatusChoices.Active.choices)
|
||||||
@@ -558,6 +575,19 @@ class AdoptionNotice(models.Model):
|
|||||||
def is_awaiting_action(self):
|
def is_awaiting_action(self):
|
||||||
return self.adoption_notice_status in self._values_of(AdoptionNoticeStatusChoices.AwaitingAction.choices)
|
return self.adoption_notice_status in self._values_of(AdoptionNoticeStatusChoices.AwaitingAction.choices)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status_description_short(self):
|
||||||
|
if self.is_active:
|
||||||
|
return _("Vermittlung aktiv")
|
||||||
|
elif self.is_disabled:
|
||||||
|
return _("Vermittlung gesperrt")
|
||||||
|
elif self.is_closed:
|
||||||
|
return _("Vermittlung geschlossen")
|
||||||
|
elif self.is_awaiting_action:
|
||||||
|
return _("Wartet auf Freigabe von Moderator*innen")
|
||||||
|
else:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def status_description(self):
|
def status_description(self):
|
||||||
return AdoptionNoticeStatusChoicesDescriptions.mapping[self.adoption_notice_status]
|
return AdoptionNoticeStatusChoicesDescriptions.mapping[self.adoption_notice_status]
|
||||||
@@ -607,7 +637,7 @@ class Animal(models.Model):
|
|||||||
verbose_name = _('Tier')
|
verbose_name = _('Tier')
|
||||||
verbose_name_plural = _('Tiere')
|
verbose_name_plural = _('Tiere')
|
||||||
|
|
||||||
date_of_birth = models.DateField(verbose_name=_('Geburtsdatum'))
|
date_of_birth = models.DateField(verbose_name=_('Geburtsdatum'), null=True, blank=True)
|
||||||
name = models.CharField(max_length=200, verbose_name=_('Name'))
|
name = models.CharField(max_length=200, verbose_name=_('Name'))
|
||||||
description = models.TextField(null=True, blank=True, verbose_name=_('Beschreibung'))
|
description = models.TextField(null=True, blank=True, verbose_name=_('Beschreibung'))
|
||||||
species = models.ForeignKey(Species, on_delete=models.PROTECT, verbose_name=_("Tierart"))
|
species = models.ForeignKey(Species, on_delete=models.PROTECT, verbose_name=_("Tierart"))
|
||||||
@@ -621,18 +651,25 @@ class Animal(models.Model):
|
|||||||
owner = models.ForeignKey(User, on_delete=models.CASCADE)
|
owner = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
history = HistoricalRecords()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.name}"
|
return f"{self.name}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def age(self):
|
def age(self):
|
||||||
|
if self.date_of_birth:
|
||||||
return timezone.now().today().date() - self.date_of_birth
|
return timezone.now().today().date() - self.date_of_birth
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hr_age(self):
|
def hr_age(self):
|
||||||
"""Returns a human-readable age based on the date of birth."""
|
"""Returns a human-readable age based on the date of birth."""
|
||||||
|
if self.date_of_birth:
|
||||||
return misc.age_as_hr_string(self.age)
|
return misc.age_as_hr_string(self.age)
|
||||||
|
else:
|
||||||
|
return _("Unbekannt")
|
||||||
|
|
||||||
def get_photo(self):
|
def get_photo(self):
|
||||||
"""
|
"""
|
||||||
@@ -684,6 +721,7 @@ class SearchSubscription(models.Model):
|
|||||||
max_distance = models.IntegerField(choices=DistanceChoices.choices, null=True)
|
max_distance = models.IntegerField(choices=DistanceChoices.choices, null=True)
|
||||||
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Zuletzt geändert am"))
|
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Zuletzt geändert am"))
|
||||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Erstellt am"))
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Erstellt am"))
|
||||||
|
history = HistoricalRecords()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.location and self.max_distance:
|
if self.location and self.max_distance:
|
||||||
@@ -714,6 +752,7 @@ class Rule(models.Model):
|
|||||||
"Identifikator haben"))
|
"Identifikator haben"))
|
||||||
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Zuletzt geändert am"))
|
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Zuletzt geändert am"))
|
||||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Erstellt am"))
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Erstellt am"))
|
||||||
|
history = HistoricalRecords()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
@@ -739,6 +778,7 @@ class Report(models.Model):
|
|||||||
user_comment = models.TextField(blank=True, verbose_name=_("Kommentar/Zusätzliche Information"))
|
user_comment = models.TextField(blank=True, verbose_name=_("Kommentar/Zusätzliche Information"))
|
||||||
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Zuletzt geändert am"))
|
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Zuletzt geändert am"))
|
||||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Erstellt am"))
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Erstellt am"))
|
||||||
|
history = HistoricalRecords()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"[{self.status}]: {self.user_comment:.20}"
|
return f"[{self.status}]: {self.user_comment:.20}"
|
||||||
@@ -825,6 +865,7 @@ class ModerationAction(models.Model):
|
|||||||
# Only visible to moderator
|
# Only visible to moderator
|
||||||
private_comment = models.TextField(blank=True)
|
private_comment = models.TextField(blank=True)
|
||||||
report = models.ForeignKey(Report, on_delete=models.CASCADE)
|
report = models.ForeignKey(Report, on_delete=models.CASCADE)
|
||||||
|
history = HistoricalRecords()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"[{self.action}]: {self.public_comment}"
|
return f"[{self.action}]: {self.public_comment}"
|
||||||
@@ -846,6 +887,7 @@ class Text(models.Model):
|
|||||||
content = models.TextField(verbose_name="Inhalt")
|
content = models.TextField(verbose_name="Inhalt")
|
||||||
language = models.ForeignKey(Language, verbose_name="Sprache", on_delete=models.PROTECT)
|
language = models.ForeignKey(Language, verbose_name="Sprache", on_delete=models.PROTECT)
|
||||||
text_code = models.CharField(max_length=24, verbose_name="Text code", blank=True)
|
text_code = models.CharField(max_length=24, verbose_name="Text code", blank=True)
|
||||||
|
history = HistoricalRecords()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Text"
|
verbose_name = "Text"
|
||||||
@@ -889,6 +931,7 @@ class Announcement(Text):
|
|||||||
INFO: "info",
|
INFO: "info",
|
||||||
}
|
}
|
||||||
type = models.CharField(choices=TYPES, max_length=100, default=INFO)
|
type = models.CharField(choices=TYPES, max_length=100, default=INFO)
|
||||||
|
history = HistoricalRecords()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_active(self):
|
def is_active(self):
|
||||||
@@ -935,6 +978,7 @@ class Comment(models.Model):
|
|||||||
adoption_notice = models.ForeignKey(AdoptionNotice, on_delete=models.CASCADE, verbose_name=_('Vermittlung'))
|
adoption_notice = models.ForeignKey(AdoptionNotice, on_delete=models.CASCADE, verbose_name=_('Vermittlung'))
|
||||||
text = models.TextField(verbose_name="Inhalt")
|
text = models.TextField(verbose_name="Inhalt")
|
||||||
reply_to = models.ForeignKey("self", verbose_name="Antwort auf", blank=True, null=True, on_delete=models.CASCADE)
|
reply_to = models.ForeignKey("self", verbose_name="Antwort auf", blank=True, null=True, on_delete=models.CASCADE)
|
||||||
|
history = HistoricalRecords()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.user} at {self.created_at.strftime('%H:%M %d.%m.%y')}: {self.text:.10}"
|
return f"{self.user} at {self.created_at.strftime('%H:%M %d.%m.%y')}: {self.text:.10}"
|
||||||
@@ -1006,6 +1050,7 @@ class Subscriptions(models.Model):
|
|||||||
help_text=_("Vermittlung die abonniert wurde"))
|
help_text=_("Vermittlung die abonniert wurde"))
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
history = HistoricalRecords()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.owner} - {self.adoption_notice}"
|
return f"{self.owner} - {self.adoption_notice}"
|
||||||
@@ -1052,8 +1097,10 @@ class SpeciesSpecificURL(models.Model):
|
|||||||
verbose_name_plural = _("Tierartspezifische URLs")
|
verbose_name_plural = _("Tierartspezifische URLs")
|
||||||
|
|
||||||
species = models.ForeignKey(Species, on_delete=models.CASCADE, verbose_name=_("Tierart"))
|
species = models.ForeignKey(Species, on_delete=models.CASCADE, verbose_name=_("Tierart"))
|
||||||
rescue_organization = models.ForeignKey(RescueOrganization, on_delete=models.CASCADE,
|
rescue_organization = models.ForeignKey(RescueOrganization,
|
||||||
verbose_name=_("Tierschutzorganisation"))
|
on_delete=models.CASCADE,
|
||||||
|
verbose_name=_("Tierschutzorganisation"),
|
||||||
|
related_name="species_specific_urls")
|
||||||
url = models.URLField(verbose_name=_("Tierartspezifische URL"))
|
url = models.URLField(verbose_name=_("Tierartspezifische URL"))
|
||||||
|
|
||||||
|
|
||||||
@@ -1067,6 +1114,7 @@ class SocialMediaPost(models.Model):
|
|||||||
choices=PlatformChoices.choices)
|
choices=PlatformChoices.choices)
|
||||||
adoption_notice = models.ForeignKey(AdoptionNotice, on_delete=models.CASCADE, verbose_name=_('Vermittlung'))
|
adoption_notice = models.ForeignKey(AdoptionNotice, on_delete=models.CASCADE, verbose_name=_('Vermittlung'))
|
||||||
url = models.URLField(verbose_name=_("URL"))
|
url = models.URLField(verbose_name=_("URL"))
|
||||||
|
history = HistoricalRecords()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_an_to_post():
|
def get_an_to_post():
|
||||||
|
|||||||
@@ -22,12 +22,24 @@ $confirm: hsl(133deg, 100%, calc(41% + 0%));
|
|||||||
background-color: $grey-light !important;
|
background-color: $grey-light !important;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navbar-burger {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
background-color: $grey-dark;
|
background-color: $grey-dark;
|
||||||
}
|
}
|
||||||
a.card-footer-item.is-danger {
|
div.card-footer-item.is-danger {
|
||||||
color: black;
|
background-color: #5a212d;
|
||||||
}
|
}
|
||||||
|
div.card-footer-item.is-warning {
|
||||||
|
background-color: #523e13;
|
||||||
|
}
|
||||||
|
div.card-footer-item.is-confirm {
|
||||||
|
background-color: #00420f;
|
||||||
|
}
|
||||||
|
|
||||||
.tag {
|
.tag {
|
||||||
color: $grey-dark;
|
color: $grey-dark;
|
||||||
background-color: $grey-light;
|
background-color: $grey-light;
|
||||||
@@ -245,6 +257,34 @@ IMAGES
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cover {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(192, 192, 192, 0.75);
|
||||||
|
z-index: 99;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 500ms;
|
||||||
|
border-radius: inherit; // ensure border radius of cards is followed, see https://css-tricks.com/preventing-child-background-overflow-with-inherited-border-radii/
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover:hover {
|
||||||
|
transition: opacity 500ms;
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
// For being able to click the image and for accessibility the cover should have display: none;
|
||||||
|
// This is accepted by W3C: https://front-end.social/@mia/109433817951030826
|
||||||
|
// However this does not yet seem to be supported to be animated by browsers.
|
||||||
|
// Chrome claims to have shipped it: https://chromestatus.com/feature/5154958272364544
|
||||||
|
// However I couldn't get it to work there too.
|
||||||
|
// display: none;
|
||||||
|
// transition: opacity 1000ms, display 100ms;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
AN Cards
|
AN Cards
|
||||||
@@ -322,7 +362,6 @@ AN Cards
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.notification-container {
|
.notification-container {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@@ -33,6 +33,11 @@
|
|||||||
<i class="fas fa-search fa-fw"></i> {% trans 'Suchen' %}
|
<i class="fas fa-search fa-fw"></i> {% trans 'Suchen' %}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
<hr>
|
||||||
|
<form method="post" autocomplete="off">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ org_name_search_form }}
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -69,4 +74,57 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
const $nameInput = $("#id_name");
|
||||||
|
|
||||||
|
$nameInput.wrap("<div class='dropdown' id='location-result-list'></div>");
|
||||||
|
const dropdown = $("#location-result-list");
|
||||||
|
$nameInput.wrap("<div class='dropdown-trigger'></div>");
|
||||||
|
$("<div class='dropdown-content' id='results'></div>").insertAfter($nameInput);
|
||||||
|
const $resultsList = $("#results");
|
||||||
|
$resultsList.wrap("<div class='dropdown-menu'></div>");
|
||||||
|
|
||||||
|
|
||||||
|
$nameInput.on("input", function () {
|
||||||
|
const query = $.trim($nameInput.val());
|
||||||
|
|
||||||
|
if (query.length < 3) {
|
||||||
|
dropdown.removeClass("is-active");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "{% api_base_url %}organizations/",
|
||||||
|
data: {
|
||||||
|
search: query
|
||||||
|
},
|
||||||
|
method: "GET",
|
||||||
|
dataType: "json",
|
||||||
|
success: function (data) {
|
||||||
|
$resultsList.empty();
|
||||||
|
dropdown.addClass("is-active");
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
const orgs = data.slice(0, 5);
|
||||||
|
|
||||||
|
$.each(orgs, function (index, org) {
|
||||||
|
const $listItem = $("<a>")
|
||||||
|
.addClass("dropdown-item")
|
||||||
|
.addClass("result-item")
|
||||||
|
.attr('href', org.url)
|
||||||
|
.text(org.name);
|
||||||
|
|
||||||
|
$resultsList.append($listItem);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function () {
|
||||||
|
$resultsList.html('<li class="result-item">{% trans 'Error fetching data. Please try again.' %}</li>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -114,7 +114,17 @@
|
|||||||
<i class="fas fa-chart-line fa-fw"></i> {% trans 'Aufrufe' %}
|
<i class="fas fa-chart-line fa-fw"></i> {% trans 'Aufrufe' %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<hr class="dropdown-divider">
|
<hr class="dropdown-divider">
|
||||||
|
|
||||||
|
<a class="dropdown-item"
|
||||||
|
href="{% url 'adoption-notice-social-media-template-selection' adoption_notice_id=adoption_notice.pk %}">
|
||||||
|
<i class="fab fa-instagram fa-fw"
|
||||||
|
aria-hidden="true"></i> {% trans 'Social Media Vorlagen' %}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<hr class="dropdown-divider">
|
||||||
|
|
||||||
<a class="dropdown-item is-warning"
|
<a class="dropdown-item is-warning"
|
||||||
href="{% url adoption_notice_meta|admin_urlname:'change' adoption_notice.pk %}">
|
href="{% url adoption_notice_meta|admin_urlname:'change' adoption_notice.pk %}">
|
||||||
<i class="fa-solid fa-tools fa-fw"></i> Admin interface
|
<i class="fa-solid fa-tools fa-fw"></i> Admin interface
|
||||||
@@ -164,6 +174,9 @@
|
|||||||
{% if adoption_notice.get_photos %}
|
{% if adoption_notice.get_photos %}
|
||||||
<div class="column block">
|
<div class="column block">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
{% if not adoption_notice.is_active %}
|
||||||
|
<div class="cover">{{ adoption_notice.status_description_short }}</div>
|
||||||
|
{% endif %}
|
||||||
<div class="grid card-content">
|
<div class="grid card-content">
|
||||||
<div class="gallery">
|
<div class="gallery">
|
||||||
{% with photo=adoption_notice.get_photos.0 %}
|
{% with photo=adoption_notice.get_photos.0 %}
|
||||||
|
|||||||
@@ -24,11 +24,20 @@
|
|||||||
<div class="block">
|
<div class="block">
|
||||||
{% include "fellchensammlung/partials/rescue_orgs/partial-rescue-organization-contact.html" %}
|
{% include "fellchensammlung/partials/rescue_orgs/partial-rescue-organization-contact.html" %}
|
||||||
</div>
|
</div>
|
||||||
|
{% trust_level "MODERATOR" as coordinator_trust_level %}
|
||||||
|
{% if request.user.trust_level >= coordinator_trust_level %}
|
||||||
|
<div class="block">
|
||||||
|
<a class="button is-primary is-fullwidth"
|
||||||
|
href="{% url 'rescue-organization-edit' rescue_organization_id=org.pk %}">
|
||||||
|
<i class="fa-solid fa-tools fa-fw"></i> {% translate 'Bearbeiten' %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<a class="button is-warning is-fullwidth" href="{% url org_meta|admin_urlname:'change' org.pk %}">
|
<a class="button is-warning is-fullwidth" href="{% url org_meta|admin_urlname:'change' org.pk %}">
|
||||||
<i class="fa-solid fa-tools fa-fw"></i> Admin interface
|
<i class="fa-solid fa-tools fa-fw"></i> Admin interface
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
{% include "fellchensammlung/partials/partial-map.html" %}
|
{% include "fellchensammlung/partials/partial-map.html" %}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
<div class="block">
|
<div class="block">
|
||||||
<h2 class="title is-2">{% trans 'Profil verwalten' %}</h2>
|
<h2 class="title is-2">{% trans 'Profil verwalten' %}</h2>
|
||||||
<div class="block"><strong>{% translate "E-Mail" %}:</strong> {{ user.email }}</div>
|
<div class="block"><strong>{% translate "E-Mail" %}:</strong> {{ user.email }}</div>
|
||||||
|
{% if user.id is request.user.id %}
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<div class="field is-grouped is-grouped-multiline">
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
@@ -53,7 +54,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if user.id is request.user.id %}
|
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<h2 class="title is-2">{% trans 'Einstellungen' %}</h2>
|
<h2 class="title is-2">{% trans 'Einstellungen' %}</h2>
|
||||||
<form class="block" action="" method="POST">
|
<form class="block" action="" method="POST">
|
||||||
@@ -98,6 +98,27 @@
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if show_mod_actions %}
|
||||||
|
<div class="block">
|
||||||
|
<h2 class="title is-2">{% trans 'Moderation' %}</h2>
|
||||||
|
<div class="block">
|
||||||
|
<p><strong>{% translate 'Moderationsnotizen' %}:</strong> {{ user.mod_notes }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="block">
|
||||||
|
{% if user.is_active %}
|
||||||
|
<a href="{% url 'user-deactivate' user_id=user.pk %}" class="button is-danger">
|
||||||
|
{% translate 'User deaktivieren' %}
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'user-activate' user_id=user.pk %}" class="button is-info">
|
||||||
|
{% translate 'User aktivieren' %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<h2 class="title is-2">{% translate 'Benachrichtigungen' %}</h2>
|
<h2 class="title is-2">{% translate 'Benachrichtigungen' %}</h2>
|
||||||
{% include "fellchensammlung/lists/list-notifications.html" %}
|
{% include "fellchensammlung/lists/list-notifications.html" %}
|
||||||
@@ -105,8 +126,7 @@
|
|||||||
<h2 class="title is-2">{% translate 'Abonnierte Suchen' %}</h2>
|
<h2 class="title is-2">{% translate 'Abonnierte Suchen' %}</h2>
|
||||||
{% include "fellchensammlung/lists/list-search-subscriptions.html" %}
|
{% include "fellchensammlung/lists/list-search-subscriptions.html" %}
|
||||||
|
|
||||||
<h2 class="title is-2">{% translate 'Meine Vermittlungen' %}</h2>
|
<h2 class="title is-2">{% translate 'Vermittlungen' %}</h2>
|
||||||
{% include "fellchensammlung/lists/list-adoption-notices.html" %}
|
{% include "fellchensammlung/lists/list-adoption-notices.html" %}
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -7,9 +7,14 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 class="title is-1">403 Forbidden</h1>
|
<h1 class="title is-1">403 Forbidden</h1>
|
||||||
<p>
|
<p>
|
||||||
|
{% if error_message %}
|
||||||
|
{{ error_message }}
|
||||||
|
{% else %}
|
||||||
{% blocktranslate %}
|
{% blocktranslate %}
|
||||||
Diese Aktion ist dir nicht erlaubt. Logge dich ein oder nutze einen anderen Account. Wenn du denkst, dass hier
|
Diese Aktion ist dir nicht erlaubt. Logge dich ein oder nutze einen anderen Account. Wenn du denkst,
|
||||||
|
dass hier
|
||||||
ein Fehler vorliegt, kontaktiere das Team!
|
ein Fehler vorliegt, kontaktiere das Team!
|
||||||
{% endblocktranslate %}
|
{% endblocktranslate %}
|
||||||
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -102,6 +102,10 @@
|
|||||||
<a class="nav-link " href="{% url "modtools" %}">
|
<a class="nav-link " href="{% url "modtools" %}">
|
||||||
{% translate 'Moderationstools' %}
|
{% translate 'Moderationstools' %}
|
||||||
</a>
|
</a>
|
||||||
|
<br>
|
||||||
|
<a class="nav-link " href="{% url "rescue-organization-create" %}">
|
||||||
|
{% translate 'Tierschutzorganisation hinzufügen' %}
|
||||||
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<br/>
|
<br/>
|
||||||
{% if request.user.is_superuser %}
|
{% if request.user.is_superuser %}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{% extends "fellchensammlung/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block description %}
|
||||||
|
<meta name="description" content="{% trans 'User aktivieren' %}">
|
||||||
|
{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="title is-1">{% trans 'User aktivieren' %}</h1>
|
||||||
|
{% blocktranslate %}
|
||||||
|
Hier kannst du einen User manuell aktivieren und optional eine Notiz hinterlassen.
|
||||||
|
{% endblocktranslate %}
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form }}
|
||||||
|
<button class="button is-info is-fullwidth" type="submit">{% translate "Aktivieren" %}</button>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{% extends "fellchensammlung/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block description %}
|
||||||
|
<meta name="description" content="{% trans 'User deaktivieren' %}">
|
||||||
|
{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="title is-1">{% trans 'User deaktivieren' %}</h1>
|
||||||
|
{% url "about" as rule_url %}
|
||||||
|
{% blocktranslate %}
|
||||||
|
Wenn dieser User unseren <a href='{{ rule_url }}'>Regeln</a> zuwider gehandelt hat oder seit langem inaktiv
|
||||||
|
ist, kannst du ihn deaktivieren.
|
||||||
|
{% endblocktranslate %}
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form }}
|
||||||
|
<button class="button is-danger is-fullwidth" type="submit">{% translate "Deaktivieren" %}</button>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{% extends "fellchensammlung/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load widget_tweaks %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="title is-1">
|
||||||
|
{% if rescue_org %}
|
||||||
|
{{ rescue_org.name }}
|
||||||
|
{% else %}
|
||||||
|
{% translate 'Tierschutzorganisation hinzufügen' %}
|
||||||
|
{% endif %}
|
||||||
|
</h1>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
{{ form }}
|
||||||
|
<input class="button is-primary" type="submit" value="{% translate "Speichern" %}">
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 54 KiB |
@@ -6,21 +6,7 @@
|
|||||||
<h1 class="title is-1">{% translate 'Vermittlung deaktivieren' %}</h1>
|
<h1 class="title is-1">{% translate 'Vermittlung deaktivieren' %}</h1>
|
||||||
<form method="post" enctype="multipart/form-data">
|
<form method="post" enctype="multipart/form-data">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="field">
|
{{ form }}
|
||||||
<label class="label" for="reason_for_closing">{% translate 'Warum schließt du die Vermittlung?' %}</label>
|
|
||||||
<div class="control">
|
|
||||||
<div class="select">
|
|
||||||
<select id="reason_for_closing" name="reason_for_closing">
|
|
||||||
<option value="closed_successful_with_notfellchen">{% translate 'Vermittelt mit Hilfe von Notfellchen' %}</option>
|
|
||||||
<option value="closed_successful_without_notfellchen">{% translate 'Vermittelt ohne Hilfe von Notfellchen' %}</option>
|
|
||||||
<option value="closed_for_other_adoption_notice">{% translate 'Vermittlung zugunsten einer anderen geschlossen' %}</option>
|
|
||||||
<option value="closed_not_open_for_adoption_anymore">{% translate 'Nicht mehr zu vermitteln (z.B. aufgrund von Krankheit)' %}</option>
|
|
||||||
<option value="closed_animal_died">{% translate 'Tod des Tiers' %}</option>
|
|
||||||
<option value="closed_other">{% translate 'Anderer Grund' %}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input class="button is-warning" type="submit" value="{% translate "Vermittlung deaktivieren" %}">
|
<input class="button is-warning" type="submit" value="{% translate "Vermittlung deaktivieren" %}">
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
{% extends "fellchensammlung/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load widget_tweaks %}
|
||||||
|
{% load admin_urls %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
<title>Social Media Post für {{ adoption_notice }}</title>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="title is-1">Social Media Post für {{ adoption_notice }}</h1>
|
||||||
|
<div class="columns block">
|
||||||
|
<div class="column">
|
||||||
|
<div class="box">
|
||||||
|
{% include 'fellchensammlung/partials/social_media/post-to-fedi.html' %}
|
||||||
|
<p class="block">
|
||||||
|
{% blocktranslate %}
|
||||||
|
|
||||||
|
Die Vermittlung wird auf unserem Fediverse-Account gepostet
|
||||||
|
{% endblocktranslate %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<div class="box block">
|
||||||
|
<a href="{% url 'adoption-notice-story-pic' adoption_notice.pk %}"
|
||||||
|
class="button is-primary is-fullwidth">
|
||||||
|
<i class="fab fa-instagram fa-fw"></i> {% trans 'Instagram Storyvorlage' %}
|
||||||
|
</a>
|
||||||
|
<p class="block">
|
||||||
|
{% blocktranslate %}
|
||||||
|
Eine Vorlage für eine Instagram-Story im Format 1080x1980px.
|
||||||
|
{% endblocktranslate %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<div class="box block">
|
||||||
|
<a href="{% url 'adoption-notice-sharepic' adoption_notice.pk %}"
|
||||||
|
class="button is-primary block is-fullwidth">
|
||||||
|
<i class="fab fa-instagram fa-fw"></i> {% trans 'Instagram Post' %}
|
||||||
|
</a>
|
||||||
|
<p class="block">
|
||||||
|
{% blocktranslate %}
|
||||||
|
Eine Vorlage für eine Instagram-Post mit einer Slide pro Tier.
|
||||||
|
{% endblocktranslate %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="block">
|
||||||
|
<p>
|
||||||
|
{% blocktranslate %}
|
||||||
|
Die Vorlagen werden idealerweise im Grafikprogramm Inkscape weiterbearbeitet.
|
||||||
|
{% endblocktranslate %}
|
||||||
|
</p>
|
||||||
|
<a href="https://inkscape.org/" class="button is-link block is-fullwidth">
|
||||||
|
<i class="fab fa-inkscape fa-fw"></i>
|
||||||
|
{% translate 'Inkscape herunterladen' %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -20,39 +20,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="block">
|
<div class="block">
|
||||||
{% if action_was_posting %}
|
{% include "fellchensammlung/partials/social_media/post-to-fedi.html" %}
|
||||||
{% if posted_successfully %}
|
|
||||||
<div class="message is-success">
|
|
||||||
<div class="message-header">
|
|
||||||
{% translate 'Vermittlung gepostet' %}
|
|
||||||
</div>
|
|
||||||
<div class="message-body">
|
|
||||||
{% blocktranslate with post_url=post.url %}
|
|
||||||
Link zum Post: <a href={{ post_url }}>{{ post_url }}</a>
|
|
||||||
{% endblocktranslate %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div class="message is-danger">
|
|
||||||
<div class="message-header">
|
|
||||||
{% translate 'Vermittlung konnte nicht gepostet werden' %}
|
|
||||||
</div>
|
|
||||||
<div class="message-body">
|
|
||||||
{{ error_message }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<form class="cell" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" name="action" value="post_to_fedi">
|
|
||||||
<button class="button is-fullwidth is-warning is-primary" type="submit" id="submit">
|
|
||||||
<i class="fa-solid fa-bullhorn fa-fw"></i> {% translate "Vermittlung ins Fediverse posten" %}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
{% translate 'Adoptionsprozess' %}
|
{% translate 'Adoptionsprozess' %}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
{% if not adoption_notice.is_active %}
|
||||||
|
<div class="cover">{{ adoption_notice.status_description_short }}</div>
|
||||||
|
{% endif %}
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
{% include adoption_process_template %}
|
{% include adoption_process_template %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -31,5 +31,14 @@
|
|||||||
{{ adoption_notice.status_description }}
|
{{ adoption_notice.status_description }}
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
{% elif adoption_notice.is_disabled %}
|
||||||
|
<article class="message is-warning">
|
||||||
|
<div class="message-header">
|
||||||
|
<p>{% translate 'Vermittlung wurde gesperrt' %}</p>
|
||||||
|
</div>
|
||||||
|
<div class="message-body content">
|
||||||
|
{{ adoption_notice.status_description }}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load custom_tags %}
|
{% load custom_tags %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<a href="{{ adoption_notice.get_absolute_url }}" class="card-header">
|
<a href="{{ adoption_notice.get_absolute_url }}" class="card-header" target="_blank">
|
||||||
<div class="card-header-title">
|
<div class="card-header-title">
|
||||||
{{ adoption_notice.name }}
|
{{ adoption_notice.name }}
|
||||||
</div>
|
</div>
|
||||||
@@ -17,25 +17,21 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<div class="card-footer-item is-confirm">
|
<form class="card-footer-item is-confirm" method="post">
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden"
|
<input type="hidden"
|
||||||
name="adoption_notice_id"
|
name="adoption_notice_id"
|
||||||
value="{{ adoption_notice.pk }}">
|
value="{{ adoption_notice.pk }}">
|
||||||
<input type="hidden" name="action" value="checked_active">
|
<input type="hidden" name="action" value="checked_active">
|
||||||
<button class="" type="submit">{% translate "Vermittlung noch aktuell" %}</button>
|
<button style="width: 100%" type="submit">{% translate "Vermittlung noch aktuell" %}</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
<form class="card-footer-item is-warning" method="post">
|
||||||
<div class="card-footer-item is-warning">
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden"
|
<input type="hidden"
|
||||||
name="adoption_notice_id"
|
name="adoption_notice_id"
|
||||||
value="{{ adoption_notice.pk }}">
|
value="{{ adoption_notice.pk }}">
|
||||||
<input type="hidden" name="action" value="checked_inactive">
|
<input type="hidden" name="action" value="checked_inactive">
|
||||||
<button class="" type="submit">{% translate "Vermittlung inaktiv" %}</button>
|
<button style="width:100%" type="submit">{% translate "Vermittlung inaktiv" %}</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
@@ -71,7 +71,22 @@
|
|||||||
value="{{ rescue_org.pk }}">
|
value="{{ rescue_org.pk }}">
|
||||||
<input type="hidden" name="action" value="checked">
|
<input type="hidden" name="action" value="checked">
|
||||||
<button class="" type="submit">{% translate "Organisation geprüft" %}</button>
|
<button class="" type="submit">{% translate "Organisation geprüft" %}</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer-item is-warning">
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden"
|
||||||
|
name="rescue_organization_id"
|
||||||
|
value="{{ rescue_org.pk }}">
|
||||||
|
<input type="hidden" name="action" value="toggle_active_communication">
|
||||||
|
<button class="" type="submit">
|
||||||
|
{% if rescue_org.ongoing_communication %}
|
||||||
|
{% translate "Aktive Kommunikation beendet" %}
|
||||||
|
{% else %}
|
||||||
|
{% translate "Aktive Kommunikation" %}
|
||||||
|
{% endif %}
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer-item is-danger">
|
<div class="card-footer-item is-danger">
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% if action_was_posting %}
|
||||||
|
{% if posted_successfully %}
|
||||||
|
<div class="message is-success">
|
||||||
|
<div class="message-header">
|
||||||
|
{% translate 'Vermittlung gepostet' %}
|
||||||
|
</div>
|
||||||
|
<div class="message-body">
|
||||||
|
{% blocktranslate with post_url=post.url %}
|
||||||
|
Link zum Post: <a href={{ post_url }}>{{ post_url }}</a>
|
||||||
|
{% endblocktranslate %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="message is-danger">
|
||||||
|
<div class="message-header">
|
||||||
|
{% translate 'Vermittlung konnte nicht gepostet werden' %}
|
||||||
|
</div>
|
||||||
|
<div class="message-body">
|
||||||
|
{{ error_message }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form class="cell" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="action" value="post_to_fedi">
|
||||||
|
{% if adoption_notice %}
|
||||||
|
<input type="hidden" name="adoption_notice_pk" value="{{ adoption_notice.pk }}">
|
||||||
|
{% endif %}
|
||||||
|
<button class="button is-fullwidth is-warning is-primary" type="submit" id="submit">
|
||||||
|
<i class="fa-solid fa-bullhorn fa-fw"></i> {% translate "Vermittlung ins Fediverse posten" %}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
@@ -5,6 +5,7 @@ from django.template.defaultfilters import stringfilter
|
|||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
from fellchensammlung.tools.misc import time_since_as_hr_string
|
from fellchensammlung.tools.misc import time_since_as_hr_string
|
||||||
from notfellchen import settings
|
from notfellchen import settings
|
||||||
@@ -54,6 +55,11 @@ def get_oxitraffic_script_if_enabled():
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def api_base_url():
|
||||||
|
return reverse("api-base-url")
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
@stringfilter
|
@stringfilter
|
||||||
def pointdecimal(value):
|
def pointdecimal(value):
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import logging
|
import logging
|
||||||
import requests
|
import requests
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from fellchensammlung.models import SocialMediaPost, PlatformChoices
|
from fellchensammlung.models import SocialMediaPost, PlatformChoices, AdoptionNotice
|
||||||
from notfellchen import settings
|
from notfellchen import settings
|
||||||
|
|
||||||
|
|
||||||
@@ -86,7 +87,10 @@ class FediClient:
|
|||||||
|
|
||||||
|
|
||||||
def post_an_to_fedi(adoption_notice):
|
def post_an_to_fedi(adoption_notice):
|
||||||
|
try:
|
||||||
client = FediClient(settings.fediverse_access_token, settings.fediverse_api_base_url)
|
client = FediClient(settings.fediverse_access_token, settings.fediverse_api_base_url)
|
||||||
|
except AttributeError:
|
||||||
|
raise ConnectionError("Configuration for connecting to a Fediverse account is missing")
|
||||||
|
|
||||||
context = {"adoption_notice": adoption_notice}
|
context = {"adoption_notice": adoption_notice}
|
||||||
status_text = render_to_string("fellchensammlung/misc/fediverse/an-post.md", context)
|
status_text = render_to_string("fellchensammlung/misc/fediverse/an-post.md", context)
|
||||||
@@ -101,3 +105,33 @@ def post_an_to_fedi(adoption_notice):
|
|||||||
platform=PlatformChoices.FEDIVERSE,
|
platform=PlatformChoices.FEDIVERSE,
|
||||||
url=response['url'], )
|
url=response['url'], )
|
||||||
return post
|
return post
|
||||||
|
|
||||||
|
|
||||||
|
def handle_post_fedi_action(adoption_notice: AdoptionNotice = SocialMediaPost.get_an_to_post()):
|
||||||
|
if adoption_notice is not None:
|
||||||
|
logging.info(f"Posting adoption notice: {adoption_notice} ({adoption_notice.id})")
|
||||||
|
try:
|
||||||
|
post = post_an_to_fedi(adoption_notice)
|
||||||
|
context = {"action_was_posting": True, "post": post, "posted_successfully": True}
|
||||||
|
except requests.exceptions.ConnectionError as e:
|
||||||
|
logging.error(f"Could not post fediverse post: {e}")
|
||||||
|
context = {"action_was_posting": True,
|
||||||
|
"posted_successfully": False,
|
||||||
|
"error_message": _("Verbindungsfehler. Vermittlung wurde nicht gepostet")}
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
logging.error(f"Could not post fediverse post: {e}")
|
||||||
|
context = {"action_was_posting": True,
|
||||||
|
"posted_successfully": False,
|
||||||
|
"error_message": _("Fehler beim Posten. Vermittlung wurde nicht gepostet. Das kann "
|
||||||
|
"z.B. an falschen Zugangsdaten liegen. Kontaktieren einen Admin.")}
|
||||||
|
except ConnectionError as e:
|
||||||
|
logging.error(f"Could not post fediverse post: {e}")
|
||||||
|
context = {"action_was_posting": True,
|
||||||
|
"posted_successfully": False,
|
||||||
|
"error_message": _(
|
||||||
|
"Fehler beim Posten, in der Konfiguration fehlen Zugangsdaten zu einem Fediverse Account")}
|
||||||
|
else:
|
||||||
|
context = {"action_was_posting": True,
|
||||||
|
"posted_successfully": False,
|
||||||
|
"error_message": _("Keine Vermittlung zum Posten gefunden.")}
|
||||||
|
return context
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from django.template.loader import render_to_string
|
|||||||
from fellchensammlung.models import AdoptionNotice
|
from fellchensammlung.models import AdoptionNotice
|
||||||
|
|
||||||
|
|
||||||
def export_svg(adoption_notice):
|
def export_svg(adoption_notice, template_name: str = "fellchensammlung/images/adoption-notice.svg"):
|
||||||
result = render_to_string(template_name="fellchensammlung/images/adoption-notice.svg",
|
result = render_to_string(template_name=template_name,
|
||||||
context={"adoption_notice": adoption_notice, })
|
context={"adoption_notice": adoption_notice, })
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -2,14 +2,19 @@ from datetime import timedelta
|
|||||||
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from fellchensammlung.models import User, AdoptionNotice, AdoptionNoticeStatusChoices, Animal, RescueOrganization
|
from fellchensammlung.models import User, AdoptionNotice, AdoptionNoticeStatusChoices, Animal, RescueOrganization, \
|
||||||
|
AllowUseOfMaterialsChices
|
||||||
|
|
||||||
|
|
||||||
def get_rescue_org_check_stats():
|
def get_rescue_org_check_stats():
|
||||||
timeframe = timezone.now().date() - timedelta(days=14)
|
timeframe = timezone.now().date() - timedelta(days=14)
|
||||||
num_rescue_orgs_to_check = RescueOrganization.objects.filter(exclude_from_check=False).filter(
|
num_rescue_orgs_to_check = RescueOrganization.objects.filter(exclude_from_check=False,
|
||||||
|
ongoing_communication=False).exclude(
|
||||||
|
allows_using_materials=AllowUseOfMaterialsChices.USE_MATERIALS_DENIED).filter(
|
||||||
last_checked__lt=timeframe).count()
|
last_checked__lt=timeframe).count()
|
||||||
num_rescue_orgs_checked = RescueOrganization.objects.filter(exclude_from_check=False).filter(
|
num_rescue_orgs_checked = RescueOrganization.objects.filter(exclude_from_check=False,
|
||||||
|
ongoing_communication=False).exclude(
|
||||||
|
allows_using_materials=AllowUseOfMaterialsChices.USE_MATERIALS_DENIED).filter(
|
||||||
last_checked__gte=timeframe).count()
|
last_checked__gte=timeframe).count()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -81,6 +81,9 @@ def is_404(url):
|
|||||||
class RequestProfiler:
|
class RequestProfiler:
|
||||||
data = []
|
data = []
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.data = []
|
||||||
|
|
||||||
def add_status(self, status):
|
def add_status(self, status):
|
||||||
self.data.append((time.time(), status))
|
self.data.append((time.time(), status))
|
||||||
|
|
||||||
|
|||||||
@@ -69,14 +69,14 @@ class AdoptionNoticeStatusChoices:
|
|||||||
UNCHECKED = "awaiting_action_unchecked", _("Unchecked")
|
UNCHECKED = "awaiting_action_unchecked", _("Unchecked")
|
||||||
|
|
||||||
class Closed(TextChoices):
|
class Closed(TextChoices):
|
||||||
SUCCESSFUL_WITH_NOTFELLCHEN = "closed_successful_with_notfellchen", _("Successful (with Notfellchen)")
|
SUCCESSFUL = "closed_successfully", _("Erfolgreich vermittelt")
|
||||||
SUCCESSFUL_WITHOUT_NOTFELLCHEN = "closed_successful_without_notfellchen", _("Successful (without Notfellchen)")
|
ANIMAL_DIED = "closed_animal_died", _("Tier gestorben")
|
||||||
ANIMAL_DIED = "closed_animal_died", _("Animal died")
|
FOR_OTHER_ADOPTION_NOTICE = ("closed_for_other_adoption_notice",
|
||||||
FOR_OTHER_ADOPTION_NOTICE = "closed_for_other_adoption_notice", _("Closed for other adoption notice")
|
_("Vermittlung wurde zugunsten einer anderen geschlossen."))
|
||||||
NOT_OPEN_ANYMORE = "closed_not_open_for_adoption_anymore", _("Not open for adoption anymore")
|
NOT_OPEN_ANYMORE = "closed_not_open_for_adoption_anymore", _("Tier(e) stehen nicht mehr zur Vermittlung bereit.")
|
||||||
LINK_TO_MORE_INFO_NOT_REACHABLE = "closed_link_to_more_info_not_reachable", _(
|
LINK_TO_MORE_INFO_NOT_REACHABLE = "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.")
|
||||||
OTHER = "closed_other", _("Other (closed)")
|
OTHER = "closed_other", _("Anderes")
|
||||||
|
|
||||||
class Disabled(TextChoices):
|
class Disabled(TextChoices):
|
||||||
AGAINST_RULES = "disabled_against_the_rules", _("Against the rules")
|
AGAINST_RULES = "disabled_against_the_rules", _("Against the rules")
|
||||||
@@ -97,8 +97,7 @@ class AdoptionNoticeStatusChoicesDescriptions:
|
|||||||
_ansc = AdoptionNoticeStatusChoices # Mapping for readability
|
_ansc = AdoptionNoticeStatusChoices # Mapping for readability
|
||||||
mapping = {_ansc.Active.SEARCHING.value: "",
|
mapping = {_ansc.Active.SEARCHING.value: "",
|
||||||
_ansc.Active.INTERESTED: _("Jemand hat bereits Interesse an den Tieren."),
|
_ansc.Active.INTERESTED: _("Jemand hat bereits Interesse an den Tieren."),
|
||||||
_ansc.Closed.SUCCESSFUL_WITH_NOTFELLCHEN: _("Vermittlung erfolgreich abgeschlossen."),
|
_ansc.Closed.SUCCESSFUL: _("Vermittlung erfolgreich abgeschlossen."),
|
||||||
_ansc.Closed.SUCCESSFUL_WITHOUT_NOTFELLCHEN: _("Vermittlung erfolgreich abgeschlossen."),
|
|
||||||
_ansc.Closed.ANIMAL_DIED: _("Die zu vermittelnden Tiere sind über die Regenbrücke gegangen."),
|
_ansc.Closed.ANIMAL_DIED: _("Die zu vermittelnden Tiere sind über die Regenbrücke gegangen."),
|
||||||
_ansc.Closed.FOR_OTHER_ADOPTION_NOTICE: _("Vermittlung wurde zugunsten einer anderen geschlossen."),
|
_ansc.Closed.FOR_OTHER_ADOPTION_NOTICE: _("Vermittlung wurde zugunsten einer anderen geschlossen."),
|
||||||
_ansc.Closed.NOT_OPEN_ANYMORE: _("Tier(e) stehen nicht mehr zur Vermittlung bereit."),
|
_ansc.Closed.NOT_OPEN_ANYMORE: _("Tier(e) stehen nicht mehr zur Vermittlung bereit."),
|
||||||
@@ -113,8 +112,8 @@ class AdoptionNoticeStatusChoicesDescriptions:
|
|||||||
_ansc.AwaitingAction.UNCHECKED: _(
|
_ansc.AwaitingAction.UNCHECKED: _(
|
||||||
"Vermittlung deaktiviert bis sie vom Team auf Aktualität geprüft wurde."),
|
"Vermittlung deaktiviert bis sie vom Team auf Aktualität geprüft wurde."),
|
||||||
|
|
||||||
_ansc.Disabled.AGAINST_RULES: _("Vermittlung deaktiviert da sie gegen die Regeln verstößt."),
|
_ansc.Disabled.AGAINST_RULES: _("Die Vermittlung wurde gesperrt, da sie gegen die Regeln verstößt."),
|
||||||
_ansc.Disabled.OTHER: _("Vermittlung deaktiviert.")
|
_ansc.Disabled.OTHER: _("Vermittlung gesperrt.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -173,6 +173,7 @@ class AdoptionNoticeSearch:
|
|||||||
|
|
||||||
class RescueOrgSearch:
|
class RescueOrgSearch:
|
||||||
def __init__(self, request):
|
def __init__(self, request):
|
||||||
|
self.name = None
|
||||||
self.area_search = None
|
self.area_search = None
|
||||||
self.max_distance = None
|
self.max_distance = None
|
||||||
self.location = None # Can either be Location (DjangoModel) or LocationProxy
|
self.location = None # Can either be Location (DjangoModel) or LocationProxy
|
||||||
@@ -229,6 +230,7 @@ class RescueOrgSearch:
|
|||||||
return fitting_rescue_orgs
|
return fitting_rescue_orgs
|
||||||
|
|
||||||
def rescue_org_search_from_request(self, request):
|
def rescue_org_search_from_request(self, request):
|
||||||
|
# Only search if request method is get with action search
|
||||||
if request.method == 'GET' and request.GET.get("action", False) == "search":
|
if request.method == 'GET' and request.GET.get("action", False) == "search":
|
||||||
self.search_form = RescueOrgSearchForm(request.GET)
|
self.search_form = RescueOrgSearchForm(request.GET)
|
||||||
self.search_form.is_valid()
|
self.search_form.is_valid()
|
||||||
|
|||||||
@@ -30,9 +30,15 @@ urlpatterns = [
|
|||||||
path("tier/<int:animal_id>/add-photo", views.add_photo_to_animal, name="animal-add-photo"),
|
path("tier/<int:animal_id>/add-photo", views.add_photo_to_animal, name="animal-add-photo"),
|
||||||
# ex: /adoption_notice/7/
|
# ex: /adoption_notice/7/
|
||||||
path("vermittlung/<int:adoption_notice_id>/", views.adoption_notice_detail, name="adoption-notice-detail"),
|
path("vermittlung/<int:adoption_notice_id>/", views.adoption_notice_detail, name="adoption-notice-detail"),
|
||||||
|
# ex: /adoption_notice/7/social-media-templates
|
||||||
|
path("vermittlung/<int:adoption_notice_id>/social-media-templates", views.adoption_notice_social_media_templates,
|
||||||
|
name="adoption-notice-social-media-template-selection"),
|
||||||
# ex: /adoption_notice/7/sharepic
|
# ex: /adoption_notice/7/sharepic
|
||||||
path("vermittlung/<int:adoption_notice_id>/sharepic", views.adoption_notice_sharepic,
|
path("vermittlung/<int:adoption_notice_id>/sharepic", views.adoption_notice_sharepic,
|
||||||
name="adoption-notice-sharepic"),
|
name="adoption-notice-sharepic"),
|
||||||
|
# ex: /adoption_notice/7/story
|
||||||
|
path("vermittlung/<int:adoption_notice_id>/storypic", views.adoption_notice_story_pic,
|
||||||
|
name="adoption-notice-story-pic"),
|
||||||
# ex: /adoption_notice/7/edit
|
# ex: /adoption_notice/7/edit
|
||||||
path("vermittlung/<int:adoption_notice_id>/edit", views.adoption_notice_edit, name="adoption-notice-edit"),
|
path("vermittlung/<int:adoption_notice_id>/edit", views.adoption_notice_edit, name="adoption-notice-edit"),
|
||||||
# ex: /vermittlung/5/add-photo
|
# ex: /vermittlung/5/add-photo
|
||||||
@@ -41,12 +47,15 @@ urlpatterns = [
|
|||||||
# ex: /adoption_notice/2/add-animal
|
# ex: /adoption_notice/2/add-animal
|
||||||
path("vermittlung/<int:adoption_notice_id>/add-animal", views.adoption_notice_add_animal,
|
path("vermittlung/<int:adoption_notice_id>/add-animal", views.adoption_notice_add_animal,
|
||||||
name="adoption-notice-add-animal"),
|
name="adoption-notice-add-animal"),
|
||||||
path("vermittlung/<int:adoption_notice_id>/close", views.deactivate_an,
|
path("vermittlung/<int:adoption_notice_id>/close", views.close_adoption_notice,
|
||||||
name="adoption-notice-close"),
|
name="adoption-notice-close"),
|
||||||
|
|
||||||
path("tierschutzorganisationen/", views.list_rescue_organizations, name="rescue-organizations"),
|
path("tierschutzorganisationen/", views.list_rescue_organizations, name="rescue-organizations"),
|
||||||
path("tierschutzorganisationen/<int:rescue_organization_id>/", views.detail_view_rescue_organization,
|
path("tierschutzorganisationen/<int:rescue_organization_id>/", views.detail_view_rescue_organization,
|
||||||
name="rescue-organization-detail"),
|
name="rescue-organization-detail"),
|
||||||
|
path("tierschutzorganisationen/add", views.rescue_org_create_or_update, name="rescue-organization-create"),
|
||||||
|
path("tierschutzorganisationen/<int:rescue_organization_id>/edit", views.rescue_org_create_or_update,
|
||||||
|
name="rescue-organization-edit"),
|
||||||
path("tierschutzorganisationen/<int:rescue_organization_id>/exkludieren", views.exclude_from_regular_check,
|
path("tierschutzorganisationen/<int:rescue_organization_id>/exkludieren", views.exclude_from_regular_check,
|
||||||
name="rescue-organization-exclude"),
|
name="rescue-organization-exclude"),
|
||||||
path("tierschutzorganisationen/add-exclusion-reason", views.update_exclusion_reason,
|
path("tierschutzorganisationen/add-exclusion-reason", views.update_exclusion_reason,
|
||||||
@@ -90,11 +99,12 @@ urlpatterns = [
|
|||||||
###########
|
###########
|
||||||
# ex: user/1
|
# ex: user/1
|
||||||
path("user/<int:user_id>/", views.user_by_id, name="user-detail"),
|
path("user/<int:user_id>/", views.user_by_id, name="user-detail"),
|
||||||
|
path("user/<int:user_id>/deactivate/", views.user_deactivate, name="user-deactivate"),
|
||||||
|
path("user/<int:user_id>/activate/", views.user_activate, name="user-activate"),
|
||||||
path("user/me/", views.my_profile, name="user-me"),
|
path("user/me/", views.my_profile, name="user-me"),
|
||||||
path("user/notifications/", views.my_notifications, name="user-notifications"),
|
path("user/notifications/", views.my_notifications, name="user-notifications"),
|
||||||
path('user/me/export/', views.export_own_profile, name='user-me-export'),
|
path('user/me/export/', views.export_own_profile, name='user-me-export'),
|
||||||
|
|
||||||
|
|
||||||
path('change-language', views.change_language, name="change-language"),
|
path('change-language', views.change_language, name="change-language"),
|
||||||
|
|
||||||
###########
|
###########
|
||||||
@@ -120,7 +130,7 @@ urlpatterns = [
|
|||||||
###################
|
###################
|
||||||
## External Site ##
|
## External Site ##
|
||||||
###################
|
###################
|
||||||
path('bulma/external-site/', views.external_site_warning, name="external-site"),
|
path('external-site/', views.external_site_warning, name="external-site"),
|
||||||
|
|
||||||
###############
|
###############
|
||||||
## TECHNICAL ##
|
## TECHNICAL ##
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ from django.contrib.auth.decorators import user_passes_test
|
|||||||
from django.core.serializers import serialize
|
from django.core.serializers import serialize
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
import json
|
import json
|
||||||
import requests
|
|
||||||
|
|
||||||
from .mail import notify_mods_new_report
|
from .mail import notify_mods_new_report
|
||||||
from notfellchen import settings
|
from notfellchen import settings
|
||||||
@@ -22,13 +21,14 @@ from fellchensammlung import logger
|
|||||||
from .models import AdoptionNotice, Text, Animal, Rule, Image, Report, ModerationAction, \
|
from .models import AdoptionNotice, Text, Animal, Rule, Image, Report, ModerationAction, \
|
||||||
User, Location, Subscriptions, Notification, RescueOrganization, \
|
User, Location, Subscriptions, Notification, RescueOrganization, \
|
||||||
Species, Log, Timestamp, TrustLevel, SexChoicesWithAll, SearchSubscription, \
|
Species, Log, Timestamp, TrustLevel, SexChoicesWithAll, SearchSubscription, \
|
||||||
ImportantLocation, SpeciesSpecificURL, NotificationTypeChoices, SocialMediaPost
|
ImportantLocation, SpeciesSpecificURL, NotificationTypeChoices, SocialMediaPost, AllowUseOfMaterialsChices
|
||||||
from .forms import AdoptionNoticeForm, ImageForm, ReportAdoptionNoticeForm, \
|
from .forms import AdoptionNoticeForm, ImageForm, ReportAdoptionNoticeForm, \
|
||||||
CommentForm, ReportCommentForm, AnimalForm, AdoptionNoticeFormAutoAnimal, SpeciesURLForm, RescueOrgInternalComment, \
|
CommentForm, ReportCommentForm, AnimalForm, AdoptionNoticeFormAutoAnimal, SpeciesURLForm, RescueOrgInternalComment, \
|
||||||
UpdateRescueOrgRegularCheckStatus
|
UpdateRescueOrgRegularCheckStatus, UserModCommentForm, CloseAdoptionNoticeForm, RescueOrgSearchByNameForm, \
|
||||||
|
RescueOrgForm
|
||||||
from .models import Language, Announcement
|
from .models import Language, Announcement
|
||||||
from .tools import i18n, img
|
from .tools import i18n, img
|
||||||
from .tools.fedi import post_an_to_fedi
|
from .tools.fedi import handle_post_fedi_action
|
||||||
from .tools.geo import GeoAPI, zoom_level_for_radius
|
from .tools.geo import GeoAPI, zoom_level_for_radius
|
||||||
from .tools.metrics import gather_metrics_data, get_rescue_org_check_stats
|
from .tools.metrics import gather_metrics_data, get_rescue_org_check_stats
|
||||||
from .tools.admin import clean_locations, get_unchecked_adoption_notices, deactivate_unchecked_adoption_notices, \
|
from .tools.admin import clean_locations, get_unchecked_adoption_notices, deactivate_unchecked_adoption_notices, \
|
||||||
@@ -114,6 +114,13 @@ def handle_an_check_actions(request, action, adoption_notice=None):
|
|||||||
|
|
||||||
def adoption_notice_detail(request, adoption_notice_id):
|
def adoption_notice_detail(request, adoption_notice_id):
|
||||||
adoption_notice = get_object_or_404(AdoptionNotice, id=adoption_notice_id)
|
adoption_notice = get_object_or_404(AdoptionNotice, id=adoption_notice_id)
|
||||||
|
if adoption_notice.is_disabled and not user_is_owner_or_trust_level(request.user, adoption_notice):
|
||||||
|
error_message = _("Die Vermittlung wurde versteckt und ist nur Admins zugänglich. Grund dafür kann z.b. ein "
|
||||||
|
"Regelverstoß sein.")
|
||||||
|
return render(request,
|
||||||
|
"fellchensammlung/errors/403.html",
|
||||||
|
context={"error_message": error_message},
|
||||||
|
status=403)
|
||||||
adoption_notice_meta = adoption_notice._meta
|
adoption_notice_meta = adoption_notice._meta
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
try:
|
try:
|
||||||
@@ -604,6 +611,9 @@ def user_detail(request, user, token=None):
|
|||||||
user_detail_profile.add_status("Finished - returning to renderer")
|
user_detail_profile.add_status("Finished - returning to renderer")
|
||||||
if request.user.is_superuser:
|
if request.user.is_superuser:
|
||||||
context["profile"] = user_detail_profile.as_relative_with_ms
|
context["profile"] = user_detail_profile.as_relative_with_ms
|
||||||
|
if request.user.trust_level > TrustLevel.MODERATOR:
|
||||||
|
context["show_mod_actions"] = True
|
||||||
|
|
||||||
return render(request, 'fellchensammlung/details/detail-user.html', context=context)
|
return render(request, 'fellchensammlung/details/detail-user.html', context=context)
|
||||||
|
|
||||||
|
|
||||||
@@ -678,6 +688,36 @@ def my_notifications(request):
|
|||||||
return render(request, 'fellchensammlung/notifications.html', context=context)
|
return render(request, 'fellchensammlung/notifications.html', context=context)
|
||||||
|
|
||||||
|
|
||||||
|
def user_activate(request, user_id):
|
||||||
|
return user_de_activation(request, user_id, True)
|
||||||
|
|
||||||
|
|
||||||
|
def user_deactivate(request, user_id):
|
||||||
|
return user_de_activation(request, user_id, False)
|
||||||
|
|
||||||
|
|
||||||
|
def user_de_activation(request, user_id, is_to_be_active):
|
||||||
|
"""
|
||||||
|
Activates or deactivates a user
|
||||||
|
"""
|
||||||
|
user = User.objects.get(id=user_id)
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = UserModCommentForm(request.POST, instance=user)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
user_instance = form.save(commit=False)
|
||||||
|
user_instance.is_active = is_to_be_active
|
||||||
|
user_instance.save()
|
||||||
|
return redirect(reverse("user-detail", args=[user_instance.pk], ))
|
||||||
|
else:
|
||||||
|
form = UserModCommentForm(instance=user)
|
||||||
|
if is_to_be_active:
|
||||||
|
template = 'fellchensammlung/forms/form-activate-user.html'
|
||||||
|
else:
|
||||||
|
template = 'fellchensammlung/forms/form-deactivate-user.html'
|
||||||
|
return render(request, template, {'form': form})
|
||||||
|
|
||||||
|
|
||||||
@user_passes_test(user_is_trust_level_or_above)
|
@user_passes_test(user_is_trust_level_or_above)
|
||||||
def modqueue(request):
|
def modqueue(request):
|
||||||
open_reports = Report.objects.select_related("reportadoptionnotice", "reportcomment").filter(status=Report.WAITING)
|
open_reports = Report.objects.select_related("reportadoptionnotice", "reportcomment").filter(status=Report.WAITING)
|
||||||
@@ -811,6 +851,7 @@ def list_rescue_organizations(request, species=None, template='fellchensammlung/
|
|||||||
context = {"rescue_organizations_to_list": rescue_organizations_to_list,
|
context = {"rescue_organizations_to_list": rescue_organizations_to_list,
|
||||||
"show_rescue_orgs": True,
|
"show_rescue_orgs": True,
|
||||||
"elided_page_range": paginator.get_elided_page_range(page_number, on_each_side=2, on_ends=1),
|
"elided_page_range": paginator.get_elided_page_range(page_number, on_each_side=2, on_ends=1),
|
||||||
|
"org_name_search_form": RescueOrgSearchByNameForm(),
|
||||||
}
|
}
|
||||||
if org_search:
|
if org_search:
|
||||||
additional_context = {
|
additional_context = {
|
||||||
@@ -867,6 +908,9 @@ def rescue_organization_check(request, context=None):
|
|||||||
if action == "checked":
|
if action == "checked":
|
||||||
rescue_org.set_checked()
|
rescue_org.set_checked()
|
||||||
Log.objects.create(user=request.user, action="rescue_organization_checked", )
|
Log.objects.create(user=request.user, action="rescue_organization_checked", )
|
||||||
|
elif action == "toggle_active_communication":
|
||||||
|
rescue_org.ongoing_communication = not rescue_org.ongoing_communication
|
||||||
|
rescue_org.save()
|
||||||
elif action == "set_species_url":
|
elif action == "set_species_url":
|
||||||
species_url_form = SpeciesURLForm(request.POST)
|
species_url_form = SpeciesURLForm(request.POST)
|
||||||
|
|
||||||
@@ -881,7 +925,9 @@ def rescue_organization_check(request, context=None):
|
|||||||
comment_form.save()
|
comment_form.save()
|
||||||
|
|
||||||
rescue_orgs_to_check = RescueOrganization.objects.filter(exclude_from_check=False,
|
rescue_orgs_to_check = RescueOrganization.objects.filter(exclude_from_check=False,
|
||||||
ongoing_communication=False).order_by("last_checked")[:3]
|
ongoing_communication=False).exclude(
|
||||||
|
allows_using_materials=AllowUseOfMaterialsChices.USE_MATERIALS_DENIED).order_by(
|
||||||
|
"last_checked")[:3]
|
||||||
rescue_orgs_with_ongoing_communication = RescueOrganization.objects.filter(ongoing_communication=True).order_by(
|
rescue_orgs_with_ongoing_communication = RescueOrganization.objects.filter(ongoing_communication=True).order_by(
|
||||||
"updated_at")
|
"updated_at")
|
||||||
rescue_orgs_last_checked = RescueOrganization.objects.filter().order_by("-last_checked")[:10]
|
rescue_orgs_last_checked = RescueOrganization.objects.filter().order_by("-last_checked")[:10]
|
||||||
@@ -929,7 +975,7 @@ def exclude_from_regular_check(request, rescue_organization_id, source="organiza
|
|||||||
if to_be_excluded:
|
if to_be_excluded:
|
||||||
Log.objects.create(user=request.user,
|
Log.objects.create(user=request.user,
|
||||||
action="rescue_organization_excluded_from_check",
|
action="rescue_organization_excluded_from_check",
|
||||||
text=f"New status: {form.cleaned_data["regular_check_status"]}")
|
text=f"New status: {form.cleaned_data['regular_check_status']}")
|
||||||
|
|
||||||
return redirect(reverse(source))
|
return redirect(reverse(source))
|
||||||
else:
|
else:
|
||||||
@@ -959,44 +1005,74 @@ def moderation_tools_overview(request):
|
|||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
action = request.POST.get("action")
|
action = request.POST.get("action")
|
||||||
if action == "post_to_fedi":
|
if action == "post_to_fedi":
|
||||||
adoption_notice = SocialMediaPost.get_an_to_post()
|
context = handle_post_fedi_action()
|
||||||
if adoption_notice is not None:
|
|
||||||
logging.info(f"Posting adoption notice: {adoption_notice} ({adoption_notice.id})")
|
|
||||||
try:
|
|
||||||
post = post_an_to_fedi(adoption_notice)
|
|
||||||
context = {"action_was_posting": True, "post": post, "posted_successfully": True}
|
|
||||||
except requests.exceptions.ConnectionError as e:
|
|
||||||
logging.error(f"Could not post fediverse post: {e}")
|
|
||||||
context = {"action_was_posting": True,
|
|
||||||
"posted_successfully": False,
|
|
||||||
"error_message": _("Verbindungsfehler. Vermittlung wurde nicht gepostet")}
|
|
||||||
except requests.exceptions.HTTPError as e:
|
|
||||||
logging.error(f"Could not post fediverse post: {e}")
|
|
||||||
context = {"action_was_posting": True,
|
|
||||||
"posted_successfully": False,
|
|
||||||
"error_message": _("Fehler beim Posten. Vermittlung wurde nicht gepostet. Das kann "
|
|
||||||
"z.B. an falschen Zugangsdaten liegen. Kontaktieren einen Admin.")}
|
|
||||||
else:
|
|
||||||
context = {"action_was_posting": True,
|
|
||||||
"posted_successfully": False,
|
|
||||||
"error_message": _("Keine Vermittlung zum Posten gefunden.")}
|
|
||||||
return render(request, 'fellchensammlung/mod-tool-overview.html', context=context)
|
return render(request, 'fellchensammlung/mod-tool-overview.html', context=context)
|
||||||
|
|
||||||
|
|
||||||
def deactivate_an(request, adoption_notice_id):
|
def close_adoption_notice(request, adoption_notice_id):
|
||||||
adoption_notice = get_object_or_404(AdoptionNotice, pk=adoption_notice_id)
|
adoption_notice = get_object_or_404(AdoptionNotice, pk=adoption_notice_id)
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
reason_for_closing = request.POST.get("reason_for_closing")
|
form = CloseAdoptionNoticeForm(request.POST, instance=adoption_notice)
|
||||||
if reason_for_closing not in AdoptionNoticeStatusChoices.Closed.values:
|
if form.is_valid():
|
||||||
return render(request, "fellchensammlung/errors/403.html", status=403)
|
form.save()
|
||||||
adoption_notice.adoption_notice_status = reason_for_closing
|
|
||||||
adoption_notice.save()
|
|
||||||
return redirect(reverse("adoption-notice-detail", args=[adoption_notice.pk], ))
|
return redirect(reverse("adoption-notice-detail", args=[adoption_notice.pk], ))
|
||||||
context = {"adoption_notice": adoption_notice, }
|
else:
|
||||||
|
form = CloseAdoptionNoticeForm(instance=adoption_notice)
|
||||||
|
context = {"adoption_notice": adoption_notice, "form": form}
|
||||||
|
|
||||||
return render(request, 'fellchensammlung/misc/deactivate-an.html', context=context)
|
return render(request, 'fellchensammlung/misc/deactivate-an.html', context=context)
|
||||||
|
|
||||||
|
|
||||||
def adoption_notice_sharepic(request, adoption_notice_id):
|
def adoption_notice_sharepic(request, adoption_notice_id):
|
||||||
adoption_notice = get_object_or_404(AdoptionNotice, pk=adoption_notice_id)
|
adoption_notice = get_object_or_404(AdoptionNotice, pk=adoption_notice_id)
|
||||||
svg_data = img.export_svg(adoption_notice)
|
svg_data = img.export_svg(adoption_notice)
|
||||||
return HttpResponse(svg_data, content_type="image/svg+xml")
|
return HttpResponse(svg_data, content_type="image/svg+xml",
|
||||||
|
headers={"Content-Disposition": f'attachment; filename="{adoption_notice.name}-post.svg"'},)
|
||||||
|
|
||||||
|
|
||||||
|
def adoption_notice_story_pic(request, adoption_notice_id):
|
||||||
|
adoption_notice = get_object_or_404(AdoptionNotice, pk=adoption_notice_id)
|
||||||
|
svg_data = img.export_svg(adoption_notice, "fellchensammlung/images/adoption-notice-story.svg")
|
||||||
|
return HttpResponse(svg_data, content_type="image/svg+xml",
|
||||||
|
headers={"Content-Disposition": f'attachment; filename="{adoption_notice.name}-story.svg"'})
|
||||||
|
|
||||||
|
|
||||||
|
def adoption_notice_social_media_templates(request, adoption_notice_id):
|
||||||
|
context = {}
|
||||||
|
if request.method == "POST":
|
||||||
|
action = request.POST.get("action")
|
||||||
|
if action == "post_to_fedi":
|
||||||
|
context = handle_post_fedi_action()
|
||||||
|
|
||||||
|
adoption_notice = get_object_or_404(AdoptionNotice, pk=adoption_notice_id)
|
||||||
|
context["adoption_notice"] = adoption_notice
|
||||||
|
return render(request, 'fellchensammlung/misc/social-media-template-selection.html', context=context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def rescue_org_create_or_update(request, rescue_organization_id=None):
|
||||||
|
"""
|
||||||
|
Create or update a rescue organization
|
||||||
|
"""
|
||||||
|
# Only users that are mods to create or edit it
|
||||||
|
if not user_is_trust_level_or_above(request.user, TrustLevel.MODERATOR):
|
||||||
|
return HttpResponseForbidden()
|
||||||
|
if rescue_organization_id:
|
||||||
|
rescue_org = get_object_or_404(RescueOrganization, pk=rescue_organization_id)
|
||||||
|
else:
|
||||||
|
rescue_org = None
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = RescueOrgForm(request.POST, instance=rescue_org)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
rescue_org = form.save()
|
||||||
|
|
||||||
|
"""Log"""
|
||||||
|
Log.objects.create(user=request.user, action="add_rescue_org",
|
||||||
|
text=f"{request.user} hat Tierschutzorganisation {rescue_org.pk} geändert")
|
||||||
|
return redirect(reverse("rescue-organization-detail", args=[rescue_org.pk], ))
|
||||||
|
else:
|
||||||
|
form = RescueOrgForm(instance=rescue_org)
|
||||||
|
return render(request, 'fellchensammlung/forms/form-rescue-organization.html',
|
||||||
|
context={"form": form, "rescue_org": rescue_org})
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -75,8 +75,10 @@ except configparser.NoSectionError:
|
|||||||
DEBUG = config.getboolean('django', 'debug', fallback=False)
|
DEBUG = config.getboolean('django', 'debug', fallback=False)
|
||||||
|
|
||||||
# Internal IPs
|
# Internal IPs
|
||||||
raw_config_value = config.get("django", "internal_ips", fallback=[])
|
internal_ip_raw_config_value = config.get("django", "internal_ips", fallback=None)
|
||||||
INTERNAL_IPS = json.loads(raw_config_value)
|
if internal_ip_raw_config_value:
|
||||||
|
INTERNAL_IPS = json.loads(internal_ip_raw_config_value)
|
||||||
|
|
||||||
|
|
||||||
""" DATABASE """
|
""" DATABASE """
|
||||||
DB_BACKEND = config.get("database", "backend", fallback="sqlite3")
|
DB_BACKEND = config.get("database", "backend", fallback="sqlite3")
|
||||||
@@ -89,7 +91,6 @@ DB_HOST = config.get("database", "host", fallback='')
|
|||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale')]
|
LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale')]
|
||||||
|
|
||||||
|
|
||||||
""" CELERY + KEYDB """
|
""" CELERY + KEYDB """
|
||||||
CELERY_BROKER_URL = config.get("celery", "broker", fallback="redis://localhost:6379/0")
|
CELERY_BROKER_URL = config.get("celery", "broker", fallback="redis://localhost:6379/0")
|
||||||
CELERY_RESULT_BACKEND = config.get("celery", "backend", fallback="redis://localhost:6379/0")
|
CELERY_RESULT_BACKEND = config.get("celery", "backend", fallback="redis://localhost:6379/0")
|
||||||
@@ -235,6 +236,8 @@ INSTALLED_APPS = [
|
|||||||
'drf_spectacular_sidecar', # required for Django collectstatic discovery
|
'drf_spectacular_sidecar', # required for Django collectstatic discovery
|
||||||
'widget_tweaks',
|
'widget_tweaks',
|
||||||
"debug_toolbar",
|
"debug_toolbar",
|
||||||
|
'admin_extra_buttons',
|
||||||
|
'simple_history',
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
@@ -252,6 +255,7 @@ MIDDLEWARE = [
|
|||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
# allauth middleware, needs to be after message middleware
|
# allauth middleware, needs to be after message middleware
|
||||||
"allauth.account.middleware.AccountMiddleware",
|
"allauth.account.middleware.AccountMiddleware",
|
||||||
|
'simple_history.middleware.HistoryRequestMiddleware',
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'notfellchen.urls'
|
ROOT_URLCONF = 'notfellchen.urls'
|
||||||
|
|||||||
Reference in New Issue
Block a user