Compare commits
67 Commits
e81618500b
...
develop
| 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 | |||
| 151ce0d88e | |||
| e07e633651 | |||
| dd3b1fde9d | |||
| 2ffc9b4ba1 | |||
| 22eebd4586 | |||
| e589a048d3 | |||
| 392eb5a7a8 | |||
| 44fa4d4880 | |||
| 9b97cc4cb1 | |||
| 656a24ef02 | |||
| 74643db087 | |||
| 3a6fd3cee1 | |||
| 29e9d1bd8c | |||
| 3c5ca9ae00 | |||
| 3d1ad6112d | |||
| b843e67e9b | |||
| 4cab71e8fb | |||
| 969339a95f | |||
| e06efa1539 | |||
| 2fb6d2782f | |||
| f69eccd0e4 | |||
| e20e6d4b1d | |||
| 0352a60e28 | |||
| abeb14601a | |||
| f52225495d | |||
| 797b2c15f7 |
@@ -9,15 +9,14 @@ RUN apt install gettext -y
|
||||
RUN apt install libpq-dev gcc -y
|
||||
COPY . /app
|
||||
WORKDIR /app
|
||||
RUN mkdir /app/data
|
||||
RUN mkdir /app/data/static
|
||||
RUN mkdir /app/data/static -p
|
||||
RUN mkdir /app/data/media
|
||||
RUN pip install -e . # Without the -e the library static folder will not be copied by collectstatic!
|
||||
RUN pip install --no-cache-dir -e . # Without the -e the library static folder will not be copied by collectstatic!
|
||||
|
||||
RUN nf collectstatic --noinput
|
||||
RUN nf compilemessages --ignore venv
|
||||
|
||||
COPY docker/notfellchen.bash /bin/notfellchen
|
||||
COPY docker/entrypoint.sh /bin/notfellchen
|
||||
|
||||
EXPOSE 7345
|
||||
CMD ["notfellchen"]
|
||||
|
||||
@@ -15,32 +15,44 @@ class DrawioDirective(SphinxDirective):
|
||||
.. drawio::
|
||||
example-diagram.drawio.html
|
||||
example-diagram.drawio.png
|
||||
:alt: Example of a Draw.io diagram
|
||||
"""
|
||||
|
||||
has_content = False
|
||||
required_arguments = 2 # html and png
|
||||
optional_arguments = 1
|
||||
final_argument_whitespace = True # indicating if the final argument may contain whitespace
|
||||
option_spec = {
|
||||
"alt": str,
|
||||
}
|
||||
|
||||
def run(self) -> list[nodes.Node]:
|
||||
html_path = self.arguments[0]
|
||||
png_path = self.arguments[1]
|
||||
|
||||
env = self.state.document.settings.env
|
||||
builder = env.app.builder
|
||||
|
||||
# Resolve paths relative to the document
|
||||
docdir = Path(env.doc2path(env.docname)).parent
|
||||
html_rel = Path(self.arguments[0])
|
||||
png_rel = Path(self.arguments[1])
|
||||
|
||||
html_path = (docdir / html_rel).resolve()
|
||||
png_path = (docdir / png_rel).resolve()
|
||||
|
||||
alt_text = self.options.get("alt", "")
|
||||
|
||||
container = nodes.container()
|
||||
|
||||
# HTML output -> raw HTML node
|
||||
if self.builder.format == "html":
|
||||
if builder.format == "html":
|
||||
# Embed the HTML file contents directly
|
||||
try:
|
||||
html_content = html_path.read_text(encoding="utf-8")
|
||||
except OSError as e:
|
||||
msg = self.state_machine.reporter.error(f"Cannot read HTML file: {e}")
|
||||
return [msg]
|
||||
aria_attribute = f' aria-label="{alt_text}"' if alt_text else ""
|
||||
raw_html_node = nodes.raw(
|
||||
"",
|
||||
f'<div class="drawio-diagram">{open(html_path, encoding="utf-8").read()}</div>',
|
||||
f'<div class="drawio-diagram"{aria_attribute}>{html_content}</div>',
|
||||
format="html",
|
||||
)
|
||||
container += raw_html_node
|
||||
@@ -51,17 +63,12 @@ class DrawioDirective(SphinxDirective):
|
||||
|
||||
return [container]
|
||||
|
||||
@property
|
||||
def builder(self):
|
||||
# Helper to access the builder from the directive context
|
||||
return self.state.document.settings.env.app.builder
|
||||
|
||||
|
||||
def setup(app: Sphinx) -> ExtensionMetadata:
|
||||
app.add_directive("drawio", DrawioDirective)
|
||||
|
||||
return {
|
||||
"version": "0.1",
|
||||
"version": "0.2",
|
||||
"parallel_read_safe": True,
|
||||
"parallel_write_safe": True,
|
||||
}
|
||||
|
||||
BIN
docs/user/Vermittlung-Lifecycle.drawio.png
Normal file
BIN
docs/user/Vermittlung-Lifecycle.drawio.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 150 KiB |
11
docs/user/Vermittlung_Lifecycle.drawio.html
Normal file
11
docs/user/Vermittlung_Lifecycle.drawio.html
Normal file
File diff suppressed because one or more lines are too long
@@ -2,6 +2,9 @@
|
||||
Benutzerhandbuch
|
||||
****************
|
||||
|
||||
Im Benutzerhandbuch findest du Informationen zur Benutzung von `notfellchen.org <https://notfellchen.org>`_.
|
||||
Solltest du darüber hinaus Fragen haben, komm gerne auf uns zu: info@notfellchen.org
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Inhalt:
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
Tiere in Vermittlung systematisch entdecken & eintragen
|
||||
=======================================================
|
||||
|
||||
Notfellchen hat eine Liste der meisten deutschen Tierheime und anderer Tierschutzorganisationen (550, Stand Oktober 2025).
|
||||
Die meisten dieser Organisationen (412, Stand Oktober 2025) nehmen Tiere auf die bei Notfellchen eingetragen werden können.
|
||||
Notfellchen hat eine Liste der meisten deutschen Tierheime und anderer Tierschutzorganisationen.
|
||||
Die meisten dieser Organisationen nehmen Tiere auf die bei Notfellchen eingetragen werden können.
|
||||
Es ist daher das Ziel, diese Organisationen alle zwei Wochen auf neue Tiere zu prüfen.
|
||||
|
||||
|
||||
+-------------------------------------------------+---------+----------------------+
|
||||
| Gruppe | Anzahl | Zuletzt aktualisiert |
|
||||
+=================================================+=========+======================+
|
||||
| Tierschutzorganisationen im Verzeichnis | 550 | Oktober 2025 |
|
||||
+-------------------------------------------------+---------+----------------------+
|
||||
| Tierschutzorganisationen in regelmäßigerPrüfung | 412 | Oktober 2025 |
|
||||
+-------------------------------------------------+---------+----------------------+
|
||||
|
||||
.. warning::
|
||||
|
||||
Organisationen auf neue Tiere zu prüfen ist eine Funktion für Moderator\*innen. Falls du Lust hast mitzuhelfen,
|
||||
|
||||
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
|
||||
----------------------------------------
|
||||
|
||||
@@ -16,6 +16,12 @@ Ersteller*innen von Vermittlungen werden über neue Kommentare per Mail benachri
|
||||
|
||||
Kommentare können, wie Vermittlungen, gemeldet werden.
|
||||
|
||||
.. drawio::
|
||||
Vermittlung_Lifecycle.drawio.html
|
||||
Vermittlung-Lifecycle.drawio.png
|
||||
:alt: Diagramm das den Prozess der Vermittlungen zeigt.
|
||||
|
||||
|
||||
Adoption Notice Status Choices
|
||||
++++++++++++++++++++++++++++++
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ host=localhost
|
||||
[django]
|
||||
secret=CHANGE-ME
|
||||
debug=True
|
||||
internal_ips=["127.0.0.1"]
|
||||
cache=False
|
||||
|
||||
[database]
|
||||
backend=sqlite3
|
||||
@@ -28,3 +30,6 @@ django_log_level=INFO
|
||||
api_url=https://photon.hyteck.de/api
|
||||
api_format=photon
|
||||
|
||||
[security]
|
||||
totp_issuer="NF Localhost"
|
||||
webauth_allow_insecure_origin=True
|
||||
|
||||
@@ -38,7 +38,11 @@ dependencies = [
|
||||
"celery[redis]",
|
||||
"drf-spectacular[sidecar]",
|
||||
"django-widget-tweaks",
|
||||
"django-super-deduper"
|
||||
"django-super-deduper",
|
||||
"django-allauth[mfa]",
|
||||
"django_debug_toolbar",
|
||||
"django-admin-extra-buttons",
|
||||
"django-simple-history"
|
||||
]
|
||||
|
||||
dynamic = ["version", "readme"]
|
||||
@@ -54,6 +58,10 @@ docs = [
|
||||
"sphinx-rtd-theme",
|
||||
"sphinx-autobuild"
|
||||
]
|
||||
shelter-upload = [
|
||||
"osm2geojson",
|
||||
"tqdm"
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
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}")
|
||||
|
||||
|
||||
def is_not_for_adoption(tierheim):
|
||||
"""
|
||||
Returns true if the shelter is not for the purpose of adoption
|
||||
"""
|
||||
if tierheim.purpose_adoption == "no":
|
||||
return True
|
||||
elif (tierheim.purpose_adoption is None and
|
||||
(tierheim.purpose_sanctuary == "yes" or tierheim.purpose_release == "yes")):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
# TODO: take note of new get_overpass_result function which does the bulk of the new overpass query work
|
||||
def get_overpass_result(area, data_file):
|
||||
"""Build the Overpass query for fetching animal shelters in the specified area."""
|
||||
@@ -225,7 +238,13 @@ def main():
|
||||
description=get_or_none(tierheim, "opening_hours"),
|
||||
external_object_identifier=tierheim["id"],
|
||||
EXTERNAL_SOURCE_IDENTIFIER="OSM",
|
||||
purpose_adoption=get_or_none(tierheim, "animal_shelter:adoption"),
|
||||
purpose_sanctuary=get_or_none(tierheim, "animal_shelter:sanctuary"),
|
||||
purpose_release=get_or_none(tierheim, "animal_shelter:release"),
|
||||
)
|
||||
# Skip the shelter if it is not for adopting animals
|
||||
if is_not_for_adoption(th_data):
|
||||
continue
|
||||
|
||||
# Define here for later
|
||||
optional_data = ["email", "phone_number", "website", "description", "fediverse_profile", "facebook",
|
||||
@@ -250,7 +269,8 @@ def main():
|
||||
|
||||
result = requests.patch(endpoint, json=org_patch_data, headers=h)
|
||||
if result.status_code != 200:
|
||||
logging.warning(f"Updating {tierheim['properties']['name']} failed:{result.status_code} {result.json()}")
|
||||
logging.warning(
|
||||
f"Updating {tierheim['properties']['name']} failed:{result.status_code} {result.json()}")
|
||||
continue
|
||||
# Rescue organization does not exist
|
||||
else:
|
||||
@@ -268,7 +288,8 @@ def main():
|
||||
|
||||
if result.status_code != 201:
|
||||
print(f"{idx} {tierheim["properties"]["name"]}:{result.status_code} {result.json()}")
|
||||
print(f"Upload finished. Inserted {stats['num_inserted_orgs']} new orgs and updated {stats['num_updated_orgs']} orgs.")
|
||||
print(
|
||||
f"Upload finished. Inserted {stats['num_inserted_orgs']} new orgs and updated {stats['num_updated_orgs']} orgs.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -7,6 +7,9 @@ from django.utils.html import format_html
|
||||
from django.urls import reverse
|
||||
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, \
|
||||
SpeciesSpecificURL, ImportantLocation, SocialMediaPost
|
||||
|
||||
@@ -17,12 +20,34 @@ from django.utils.translation import gettext_lazy as _
|
||||
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)
|
||||
class AdoptionNoticeAdmin(admin.ModelAdmin):
|
||||
search_fields = ("name__icontains", "description__icontains")
|
||||
list_filter = ("owner",)
|
||||
search_fields = ("name__icontains", "description__icontains", "location__icontains")
|
||||
list_display = ["name", "adoption_notice_status", "owner", "organization", "last_checked_hr"]
|
||||
list_filter = ("adoption_notice_status", "owner")
|
||||
actions = ("activate",)
|
||||
|
||||
# This admin display is only needed to get a translated label of this property
|
||||
# If not present the column would show up as "last checked hr"
|
||||
@admin.display(description=_("zuletzt überprüft"))
|
||||
def last_checked_hr(self, obj):
|
||||
return obj.last_checked_hr
|
||||
|
||||
def activate(self, request, queryset):
|
||||
for obj in queryset:
|
||||
obj.adoption_notice_status = AdoptionNoticeStatusChoices.Active.SEARCHING
|
||||
@@ -33,7 +58,7 @@ class AdoptionNoticeAdmin(admin.ModelAdmin):
|
||||
|
||||
# Re-register UserAdmin
|
||||
@admin.register(User)
|
||||
class UserAdmin(admin.ModelAdmin):
|
||||
class UserAdmin(SimpleHistoryAdmin):
|
||||
search_fields = ("usernamname__icontains", "first_name__icontains", "last_name__icontains", "email__icontains")
|
||||
list_display = ("username", "email", "trust_level", "is_active", "view_adoption_notices")
|
||||
list_filter = ("is_active", "trust_level",)
|
||||
@@ -49,17 +74,7 @@ class UserAdmin(admin.ModelAdmin):
|
||||
return format_html('<a href="{}">{} Adoption Notices</a>', url, count)
|
||||
|
||||
def export_as_csv(self, request, queryset):
|
||||
meta = self.model._meta
|
||||
field_names = [field.name for field in meta.fields]
|
||||
|
||||
response = HttpResponse(content_type='text/csv')
|
||||
response['Content-Disposition'] = 'attachment; filename={}.csv'.format(meta)
|
||||
writer = csv.writer(response)
|
||||
|
||||
writer.writerow(field_names)
|
||||
for obj in queryset:
|
||||
row = writer.writerow([getattr(obj, field) for field in field_names])
|
||||
|
||||
response = export_to_csv_generic(self.model, queryset)
|
||||
return response
|
||||
|
||||
export_as_csv.short_description = _("Ausgewählte User exportieren")
|
||||
@@ -71,7 +86,7 @@ def _reported_content_link(obj):
|
||||
|
||||
|
||||
@admin.register(ReportComment)
|
||||
class ReportCommentAdmin(admin.ModelAdmin):
|
||||
class ReportCommentAdmin(SimpleHistoryAdmin):
|
||||
list_display = ["user_comment", "reported_content_link"]
|
||||
date_hierarchy = "created_at"
|
||||
|
||||
@@ -82,7 +97,7 @@ class ReportCommentAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(ReportAdoptionNotice)
|
||||
class ReportAdoptionNoticeAdmin(admin.ModelAdmin):
|
||||
class ReportAdoptionNoticeAdmin(SimpleHistoryAdmin):
|
||||
list_display = ["user_comment", "reported_content_link"]
|
||||
date_hierarchy = "created_at"
|
||||
|
||||
@@ -97,7 +112,7 @@ class SpeciesSpecificURLInline(admin.StackedInline):
|
||||
|
||||
|
||||
@admin.register(RescueOrganization)
|
||||
class RescueOrganizationAdmin(admin.ModelAdmin):
|
||||
class RescueOrganizationAdmin(SimpleHistoryAdmin):
|
||||
search_fields = ("name", "description", "internal_comment", "location_string", "location__city")
|
||||
list_display = ("name", "trusted", "allows_using_materials", "website")
|
||||
list_filter = ("allows_using_materials", "trusted", ("external_source_identifier", EmptyFieldListFilter))
|
||||
@@ -108,12 +123,12 @@ class RescueOrganizationAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(Text)
|
||||
class TextAdmin(admin.ModelAdmin):
|
||||
class TextAdmin(SimpleHistoryAdmin):
|
||||
search_fields = ("title__icontains", "text_code__icontains",)
|
||||
|
||||
|
||||
@admin.register(Comment)
|
||||
class CommentAdmin(admin.ModelAdmin):
|
||||
class CommentAdmin(SimpleHistoryAdmin):
|
||||
list_filter = ("user",)
|
||||
|
||||
|
||||
@@ -123,7 +138,7 @@ class BaseNotificationAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(SearchSubscription)
|
||||
class SearchSubscriptionAdmin(admin.ModelAdmin):
|
||||
class SearchSubscriptionAdmin(SimpleHistoryAdmin):
|
||||
list_filter = ("owner",)
|
||||
|
||||
|
||||
@@ -151,8 +166,17 @@ class IsImportantListFilter(admin.SimpleListFilter):
|
||||
|
||||
|
||||
@admin.register(Location)
|
||||
class LocationAdmin(admin.ModelAdmin):
|
||||
class LocationAdmin(SimpleHistoryAdmin):
|
||||
search_fields = ("name__icontains", "city__icontains")
|
||||
list_display = ("name", "city", "slug")
|
||||
|
||||
@admin.display(description=_("Slug"))
|
||||
def slug(self, obj):
|
||||
if obj.importantlocation:
|
||||
return obj.importantlocation.slug
|
||||
else:
|
||||
return None
|
||||
|
||||
list_filter = [IsImportantListFilter]
|
||||
inlines = [
|
||||
ImportantLocationInline,
|
||||
@@ -160,17 +184,39 @@ class LocationAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(SocialMediaPost)
|
||||
class SocialMediaPostAdmin(admin.ModelAdmin):
|
||||
class SocialMediaPostAdmin(SimpleHistoryAdmin):
|
||||
list_filter = ("platform",)
|
||||
|
||||
|
||||
admin.site.register(Animal)
|
||||
@admin.register(Log)
|
||||
class LogAdmin(ExtraButtonsMixin, admin.ModelAdmin):
|
||||
ordering = ["-created_at"]
|
||||
list_filter = ("action",)
|
||||
list_display = ("action", "user", "created_at")
|
||||
actions = ("export_as_csv",)
|
||||
|
||||
@admin.action(description=_("Ausgewählte Logs exportieren"))
|
||||
def export_as_csv(self, request, queryset):
|
||||
response = export_to_csv_generic(Log, queryset)
|
||||
return response
|
||||
|
||||
@button()
|
||||
def export_all_as_csv(self, request):
|
||||
actual_queryset = Log.objects.all()
|
||||
response = export_to_csv_generic(Log, actual_queryset)
|
||||
return response
|
||||
|
||||
@link(href="https://www.google.com/", visible=lambda btn: True)
|
||||
def invisible(self, button):
|
||||
button.visible = False
|
||||
|
||||
|
||||
admin.site.register(Animal, SimpleHistoryAdmin)
|
||||
admin.site.register(Species)
|
||||
admin.site.register(Rule)
|
||||
admin.site.register(Rule, SimpleHistoryAdmin)
|
||||
admin.site.register(Image)
|
||||
admin.site.register(ModerationAction)
|
||||
admin.site.register(ModerationAction, SimpleHistoryAdmin)
|
||||
admin.site.register(Language)
|
||||
admin.site.register(Announcement)
|
||||
admin.site.register(Subscriptions)
|
||||
admin.site.register(Log)
|
||||
admin.site.register(Announcement, SimpleHistoryAdmin)
|
||||
admin.site.register(Subscriptions, SimpleHistoryAdmin)
|
||||
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
|
||||
import math
|
||||
|
||||
@@ -144,10 +144,26 @@ class AnimalGetSerializer(serializers.ModelSerializer):
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class SpeciesSpecificURLSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = SpeciesSpecificURL
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class RescueOrganizationSerializer(serializers.ModelSerializer):
|
||||
url = serializers.SerializerMethodField()
|
||||
species_specific_urls = SpeciesSpecificURLSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = RescueOrganization
|
||||
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):
|
||||
|
||||
@@ -2,10 +2,11 @@ from django.urls import path
|
||||
from .views import (
|
||||
AdoptionNoticeApiView,
|
||||
AnimalApiView, RescueOrganizationApiView, AddImageApiView, SpeciesApiView, LocationApiView,
|
||||
AdoptionNoticeGeoJSONView, RescueOrgGeoJSONView, AdoptionNoticePerOrgApiView
|
||||
AdoptionNoticeGeoJSONView, RescueOrgGeoJSONView, AdoptionNoticePerOrgApiView, index
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
path("", index, name="api-base-url"),
|
||||
path("adoption_notice", AdoptionNoticeApiView.as_view(), name="api-adoption-notice-list"),
|
||||
path("adoption_notice.geojson", AdoptionNoticeGeoJSONView.as_view(), name="api-adoption-notice-list-geojson"),
|
||||
path("adoption_notice/<int:id>/", AdoptionNoticeApiView.as_view(), name="api-adoption-notice-detail"),
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from rest_framework.generics import ListAPIView
|
||||
|
||||
@@ -450,3 +452,7 @@ class AdoptionNoticePerOrgApiView(APIView):
|
||||
adoption_notices = temporary_an_storage
|
||||
serializer = AdoptionNoticeSerializer(adoption_notices, many=True, context={"request": request})
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
def index(request):
|
||||
return redirect(reverse("swagger-ui"))
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
from django import forms
|
||||
from django.forms.widgets import Textarea
|
||||
|
||||
from .models import AdoptionNotice, Animal, Image, ReportAdoptionNotice, ReportComment, ModerationAction, User, Species, \
|
||||
Comment, SexChoicesWithAll, DistanceChoices, SpeciesSpecificURL, RescueOrganization
|
||||
from django_registration.forms import RegistrationForm
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Submit, Layout, Fieldset, HTML, Row, Column, Field, Hidden
|
||||
from crispy_forms.layout import Submit, Layout, Fieldset
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from notfellchen.settings import MEDIA_URL
|
||||
from crispy_forms.layout import Div
|
||||
|
||||
from .tools.model_helpers import reason_for_signup_label, reason_for_signup_help_text, AdoptionNoticeStatusChoices
|
||||
|
||||
|
||||
def animal_validator(value: str):
|
||||
value = value.lower()
|
||||
animal_list = ["ratte", "farbratte", "katze", "hund", "kaninchen", "hase", "kuh", "fuchs", "cow", "rat", "cat",
|
||||
"dog", "rabbit", "fox", "fancy rat"]
|
||||
"dog", "rabbit", "fox", "fancy rat", "meerschweinchen"]
|
||||
if value not in animal_list:
|
||||
raise forms.ValidationError(_("Dieses Tier kenne ich nicht. Probier ein anderes"))
|
||||
|
||||
@@ -57,6 +59,14 @@ class AnimalForm(forms.ModelForm):
|
||||
}
|
||||
|
||||
|
||||
class UpdateRescueOrgRegularCheckStatus(forms.ModelForm):
|
||||
template_name = "fellchensammlung/forms/form_snippets.html"
|
||||
|
||||
class Meta:
|
||||
model = RescueOrganization
|
||||
fields = ["regular_check_status"]
|
||||
|
||||
|
||||
class ImageForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
if 'in_flow' in kwargs:
|
||||
@@ -105,6 +115,12 @@ class ReportCommentForm(forms.ModelForm):
|
||||
fields = ('reported_broken_rules', 'user_comment')
|
||||
|
||||
|
||||
class UserModCommentForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('mod_notes',)
|
||||
|
||||
|
||||
class CommentForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Comment
|
||||
@@ -129,6 +145,18 @@ class ModerationActionForm(forms.ModelForm):
|
||||
fields = ('action', 'public_comment', 'private_comment')
|
||||
|
||||
|
||||
class AddedRegistrationForm(forms.Form):
|
||||
reason_for_signup = forms.CharField(label=reason_for_signup_label,
|
||||
help_text=reason_for_signup_help_text,
|
||||
widget=Textarea)
|
||||
captcha = forms.CharField(validators=[animal_validator], label=_("Nenne eine bekannte Tierart"), help_text=_(
|
||||
"Bitte nenne hier eine bekannte Tierart (z.B. ein Tier das an der Leine geführt wird). Das Fragen wir dich um "
|
||||
"sicherzustellen, dass du kein Roboter bist."))
|
||||
|
||||
def signup(self, request, user):
|
||||
pass
|
||||
|
||||
|
||||
class CustomRegistrationForm(RegistrationForm):
|
||||
class Meta(RegistrationForm.Meta):
|
||||
model = User
|
||||
@@ -155,3 +183,31 @@ class RescueOrgSearchForm(forms.Form):
|
||||
location_string = forms.CharField(max_length=100, label=_("Stadt"), required=False)
|
||||
max_distance = forms.ChoiceField(choices=DistanceChoices, initial=DistanceChoices.TWENTY,
|
||||
label=_("Suchradius"))
|
||||
|
||||
|
||||
class RescueOrgSearchByNameForm(forms.Form):
|
||||
template_name = "fellchensammlung/forms/form_snippets.html"
|
||||
name = forms.CharField(max_length=100, label=_("Name der Organisation"), required=False)
|
||||
|
||||
|
||||
class CloseAdoptionNoticeForm(forms.ModelForm):
|
||||
template_name = "fellchensammlung/forms/form_snippets.html"
|
||||
|
||||
adoption_notice_status = forms.ChoiceField(choices=AdoptionNoticeStatusChoices.Closed,
|
||||
label=_("Status"),
|
||||
help_text=_("Gib den neuen Status der Vermittlung an"))
|
||||
|
||||
class Meta:
|
||||
model = AdoptionNotice
|
||||
fields = ('adoption_notice_status',)
|
||||
|
||||
|
||||
class RescueOrgForm(forms.ModelForm):
|
||||
template_name = "fellchensammlung/forms/form_snippets.html"
|
||||
|
||||
class Meta:
|
||||
model = RescueOrganization
|
||||
fields = ('name', 'allows_using_materials', 'location_string', "email", "phone_number", "website", "instagram",
|
||||
"facebook", "fediverse_profile", "internal_comment", "description", "external_source_identifier",
|
||||
"external_object_identifier",
|
||||
"parent_org")
|
||||
|
||||
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,18 @@
|
||||
# Generated by Django 5.2.1 on 2025-10-20 08:43
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0068_alter_adoptionnotice_adoption_notice_status_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='rescueorganization',
|
||||
name='regular_check_status',
|
||||
field=models.CharField(choices=[('regular_check', 'Wird regelmäßig geprüft'), ('excluded_no_online_listing', 'Exkludiert: Tiere werden nicht online gelistet'), ('excluded_other_org', 'Exkludiert: Andere Organisation wird geprüft'), ('excluded_scope', 'Exkludiert: Organisation hat nie Notfellchen-relevanten Vermittlungen'), ('excluded_other', 'Exkludiert: Anderer Grund')], default='regular_check', help_text='Organisationen können, durch ändern dieser Einstellung, von der regelmäßigen Prüfung ausgeschlossen werden.', max_length=30, verbose_name='Status der regelmäßigen Prüfung'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-16 16:50
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0069_rescueorganization_regular_check_status'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='mod_notes',
|
||||
field=models.TextField(blank=True, null=True, verbose_name='Moderationsnotizen'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='reason_for_signup',
|
||||
field=models.TextField(help_text="Wir würden gerne wissen warum du dich registrierst, ob du dich z.B. Tiere eines bestimmten Tierheim einstellen willst 'nur mal gucken' willst. Beides ist toll! Wenn du für ein Tierheim/eine Pflegestelle arbeitest kontaktieren wir dich ggf. um dir erweiterte Rechte zu geben.", verbose_name='Grund für die Registrierung'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,367 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-16 17:37
|
||||
|
||||
import django.contrib.auth.validators
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import simple_history.models
|
||||
import uuid
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0070_user_mod_notes_alter_user_reason_for_signup'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='HistoricalAdoptionNotice',
|
||||
fields=[
|
||||
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
|
||||
('created_at', models.DateField(default=django.utils.timezone.now, verbose_name='Erstellt am')),
|
||||
('updated_at', models.DateTimeField(blank=True, editable=False)),
|
||||
('last_checked', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Zuletzt überprüft am')),
|
||||
('searching_since', models.DateField(verbose_name='Sucht nach einem Zuhause seit')),
|
||||
('name', models.CharField(max_length=200, verbose_name='Titel der Vermittlung')),
|
||||
('description', models.TextField(blank=True, null=True, verbose_name='Beschreibung')),
|
||||
('further_information', models.URLField(blank=True, help_text='Verlinke hier die Quelle der Vermittlung (z.B. die Website des Tierheims)', null=True, verbose_name='Link zu mehr Informationen')),
|
||||
('group_only', models.BooleanField(default=False, verbose_name='Ausschließlich Gruppenadoption')),
|
||||
('location_string', models.CharField(max_length=200, verbose_name='Ortsangabe')),
|
||||
('adoption_notice_status', models.TextField(choices=[('active_searching', 'Searching'), ('active_interested', 'Interested'), ('awaiting_action_waiting_for_review', 'Waiting for review'), ('awaiting_action_needs_additional_info', 'Needs additional info'), ('awaiting_action_unchecked', 'Unchecked'), ('closed_successful_with_notfellchen', 'Successful (with Notfellchen)'), ('closed_successful_without_notfellchen', 'Successful (without Notfellchen)'), ('closed_animal_died', 'Animal died'), ('closed_for_other_adoption_notice', 'Closed for other adoption notice'), ('closed_not_open_for_adoption_anymore', 'Not open for adoption anymore'), ('closed_link_to_more_info_not_reachable', 'Der Link zu weiteren Informationen ist nicht mehr erreichbar.'), ('closed_other', 'Other (closed)'), ('disabled_against_the_rules', 'Against the rules'), ('disabled_other', 'Other (disabled)')], max_length=64, verbose_name='Status')),
|
||||
('adoption_process', models.TextField(blank=True, choices=[('contact_person_in_an', 'Kontaktiere die Person im Vermittlungstext')], max_length=64, null=True, verbose_name='Adoptionsprozess')),
|
||||
('history_id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('history_date', models.DateTimeField(db_index=True)),
|
||||
('history_change_reason', models.CharField(max_length=100, null=True)),
|
||||
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
|
||||
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||
('location', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.location')),
|
||||
('organization', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.rescueorganization', verbose_name='Organisation')),
|
||||
('owner', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Creator')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'historical Vermittlung',
|
||||
'verbose_name_plural': 'historical Vermittlungen',
|
||||
'ordering': ('-history_date', '-history_id'),
|
||||
'get_latest_by': ('history_date', 'history_id'),
|
||||
},
|
||||
bases=(simple_history.models.HistoricalChanges, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HistoricalAnimal',
|
||||
fields=[
|
||||
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
|
||||
('date_of_birth', models.DateField(verbose_name='Geburtsdatum')),
|
||||
('name', models.CharField(max_length=200, verbose_name='Name')),
|
||||
('description', models.TextField(blank=True, null=True, verbose_name='Beschreibung')),
|
||||
('sex', models.CharField(choices=[('F', 'Weiblich'), ('M', 'Männlich'), ('M_N', 'Männlich, kastriert'), ('F_N', 'Weiblich, kastriert'), ('I', 'Intergeschlechtlich')], max_length=20, verbose_name='Geschlecht')),
|
||||
('updated_at', models.DateTimeField(blank=True, editable=False)),
|
||||
('created_at', models.DateTimeField(blank=True, editable=False)),
|
||||
('history_id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('history_date', models.DateTimeField(db_index=True)),
|
||||
('history_change_reason', models.CharField(max_length=100, null=True)),
|
||||
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
|
||||
('adoption_notice', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.adoptionnotice')),
|
||||
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||
('owner', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||
('species', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.species', verbose_name='Tierart')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'historical Tier',
|
||||
'verbose_name_plural': 'historical Tiere',
|
||||
'ordering': ('-history_date', '-history_id'),
|
||||
'get_latest_by': ('history_date', 'history_id'),
|
||||
},
|
||||
bases=(simple_history.models.HistoricalChanges, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HistoricalAnnouncement',
|
||||
fields=[
|
||||
('text_ptr', models.ForeignKey(auto_created=True, blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, parent_link=True, related_name='+', to='fellchensammlung.text')),
|
||||
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=100, verbose_name='Titel')),
|
||||
('content', models.TextField(verbose_name='Inhalt')),
|
||||
('text_code', models.CharField(blank=True, max_length=24, verbose_name='Text code')),
|
||||
('logged_in_only', models.BooleanField(default=False)),
|
||||
('created_at', models.DateTimeField(blank=True, editable=False)),
|
||||
('updated_at', models.DateTimeField(blank=True, editable=False)),
|
||||
('publish_start_time', models.DateTimeField(verbose_name='Veröffentlichungszeitpunkt')),
|
||||
('publish_end_time', models.DateTimeField(verbose_name='Veröffentlichungsende')),
|
||||
('type', models.CharField(choices=[('important', 'important'), ('warning', 'warning'), ('info', 'info')], default='info', max_length=100)),
|
||||
('history_id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('history_date', models.DateTimeField(db_index=True)),
|
||||
('history_change_reason', models.CharField(max_length=100, null=True)),
|
||||
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
|
||||
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||
('language', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.language', verbose_name='Sprache')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'historical Banner',
|
||||
'verbose_name_plural': 'historical Banner',
|
||||
'ordering': ('-history_date', '-history_id'),
|
||||
'get_latest_by': ('history_date', 'history_id'),
|
||||
},
|
||||
bases=(simple_history.models.HistoricalChanges, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HistoricalComment',
|
||||
fields=[
|
||||
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(blank=True, editable=False)),
|
||||
('updated_at', models.DateTimeField(blank=True, editable=False)),
|
||||
('text', models.TextField(verbose_name='Inhalt')),
|
||||
('history_id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('history_date', models.DateTimeField(db_index=True)),
|
||||
('history_change_reason', models.CharField(max_length=100, null=True)),
|
||||
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
|
||||
('adoption_notice', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.adoptionnotice', verbose_name='Vermittlung')),
|
||||
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||
('reply_to', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.comment', verbose_name='Antwort auf')),
|
||||
('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Nutzer*in')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'historical Kommentar',
|
||||
'verbose_name_plural': 'historical Kommentare',
|
||||
'ordering': ('-history_date', '-history_id'),
|
||||
'get_latest_by': ('history_date', 'history_id'),
|
||||
},
|
||||
bases=(simple_history.models.HistoricalChanges, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HistoricalModerationAction',
|
||||
fields=[
|
||||
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
|
||||
('action', models.CharField(choices=[('user_banned', 'User was banned'), ('content_deleted', 'Content was deleted'), ('comment', 'Comment was added'), ('other_action_taken', 'Other action was taken'), ('no_action_taken', 'No action was taken')], max_length=30)),
|
||||
('created_at', models.DateTimeField(blank=True, editable=False)),
|
||||
('updated_at', models.DateTimeField(blank=True, editable=False)),
|
||||
('public_comment', models.TextField(blank=True)),
|
||||
('private_comment', models.TextField(blank=True)),
|
||||
('history_id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('history_date', models.DateTimeField(db_index=True)),
|
||||
('history_change_reason', models.CharField(max_length=100, null=True)),
|
||||
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
|
||||
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||
('report', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.report')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'historical Moderationsaktion',
|
||||
'verbose_name_plural': 'historical Moderationsaktionen',
|
||||
'ordering': ('-history_date', '-history_id'),
|
||||
'get_latest_by': ('history_date', 'history_id'),
|
||||
},
|
||||
bases=(simple_history.models.HistoricalChanges, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HistoricalReport',
|
||||
fields=[
|
||||
('id', models.UUIDField(db_index=True, default=uuid.uuid4, help_text='ID dieses reports', verbose_name='ID')),
|
||||
('status', models.CharField(choices=[('action taken', 'Action was taken'), ('no action taken', 'No action was taken'), ('waiting', 'Waiting for moderator action')], max_length=30)),
|
||||
('user_comment', models.TextField(blank=True, verbose_name='Kommentar/Zusätzliche Information')),
|
||||
('updated_at', models.DateTimeField(blank=True, editable=False, verbose_name='Zuletzt geändert am')),
|
||||
('created_at', models.DateTimeField(blank=True, editable=False, verbose_name='Erstellt am')),
|
||||
('history_id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('history_date', models.DateTimeField(db_index=True)),
|
||||
('history_change_reason', models.CharField(max_length=100, null=True)),
|
||||
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
|
||||
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'historical Meldung',
|
||||
'verbose_name_plural': 'historical Meldungen',
|
||||
'ordering': ('-history_date', '-history_id'),
|
||||
'get_latest_by': ('history_date', 'history_id'),
|
||||
},
|
||||
bases=(simple_history.models.HistoricalChanges, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HistoricalRescueOrganization',
|
||||
fields=[
|
||||
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200)),
|
||||
('trusted', models.BooleanField(default=False, verbose_name='Vertrauenswürdig')),
|
||||
('allows_using_materials', models.CharField(choices=[('allowed', 'Usage allowed'), ('requested', 'Usage requested'), ('denied', 'Usage denied'), ('other', "It's complicated"), ('not_asked', 'Not asked')], default='not_asked', max_length=200, verbose_name='Erlaubt Nutzung von Inhalten')),
|
||||
('location_string', models.CharField(blank=True, max_length=200, null=True, verbose_name='Ort der Organisation')),
|
||||
('instagram', models.URLField(blank=True, null=True, verbose_name='Instagram Profil')),
|
||||
('facebook', models.URLField(blank=True, null=True, verbose_name='Facebook Profil')),
|
||||
('fediverse_profile', models.URLField(blank=True, null=True, verbose_name='Fediverse Profil')),
|
||||
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='E-Mail')),
|
||||
('phone_number', models.CharField(blank=True, max_length=15, null=True, verbose_name='Telefonnummer')),
|
||||
('website', models.URLField(blank=True, null=True, verbose_name='Website')),
|
||||
('updated_at', models.DateTimeField(blank=True, editable=False)),
|
||||
('created_at', models.DateTimeField(blank=True, editable=False)),
|
||||
('last_checked', models.DateTimeField(blank=True, editable=False, verbose_name='Datum der letzten Prüfung')),
|
||||
('internal_comment', models.TextField(blank=True, null=True, verbose_name='Interner Kommentar')),
|
||||
('description', models.TextField(blank=True, null=True, verbose_name='Beschreibung')),
|
||||
('external_object_identifier', models.CharField(blank=True, max_length=200, null=True, verbose_name='External Object Identifier')),
|
||||
('external_source_identifier', models.CharField(blank=True, choices=[('OSM', 'Open Street Map')], max_length=200, null=True, verbose_name='External Source Identifier')),
|
||||
('exclude_from_check', models.BooleanField(default=False, help_text='Organisation von der manuellen Überprüfung ausschließen, z.B. weil Tiere nicht online geführt werden', verbose_name='Von Prüfung ausschließen')),
|
||||
('regular_check_status', models.CharField(choices=[('regular_check', 'Wird regelmäßig geprüft'), ('excluded_no_online_listing', 'Exkludiert: Tiere werden nicht online gelistet'), ('excluded_other_org', 'Exkludiert: Andere Organisation wird geprüft'), ('excluded_scope', 'Exkludiert: Organisation hat nie Notfellchen-relevanten Vermittlungen'), ('excluded_other', 'Exkludiert: Anderer Grund')], default='regular_check', help_text='Organisationen können, durch ändern dieser Einstellung, von der regelmäßigen Prüfung ausgeschlossen werden.', max_length=30, verbose_name='Status der regelmäßigen Prüfung')),
|
||||
('ongoing_communication', models.BooleanField(default=False, help_text='Es findet gerade Kommunikation zwischen Notfellchen und der Organisation statt.', verbose_name='In aktiver Kommunikation')),
|
||||
('twenty_id', models.UUIDField(blank=True, help_text='ID der der Organisation in Twenty', null=True, verbose_name='Twenty-ID')),
|
||||
('history_id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('history_date', models.DateTimeField(db_index=True)),
|
||||
('history_change_reason', models.CharField(max_length=100, null=True)),
|
||||
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
|
||||
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||
('location', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.location')),
|
||||
('parent_org', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.rescueorganization')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'historical Tierschutzorganisation',
|
||||
'verbose_name_plural': 'historical Tierschutzorganisationen',
|
||||
'ordering': ('-history_date', '-history_id'),
|
||||
'get_latest_by': ('history_date', 'history_id'),
|
||||
},
|
||||
bases=(simple_history.models.HistoricalChanges, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HistoricalRule',
|
||||
fields=[
|
||||
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=200)),
|
||||
('rule_text', models.TextField(verbose_name='Regeltext')),
|
||||
('rule_identifier', models.CharField(help_text='Ein eindeutiger Identifikator der Regel. Ein Regelobjekt derselben Regel in einer anderen Sprache muss den gleichen Identifikator haben', max_length=24, verbose_name='Regel-ID')),
|
||||
('updated_at', models.DateTimeField(blank=True, editable=False, verbose_name='Zuletzt geändert am')),
|
||||
('created_at', models.DateTimeField(blank=True, editable=False, verbose_name='Erstellt am')),
|
||||
('history_id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('history_date', models.DateTimeField(db_index=True)),
|
||||
('history_change_reason', models.CharField(max_length=100, null=True)),
|
||||
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
|
||||
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||
('language', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.language', verbose_name='Sprache')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'historical Regel',
|
||||
'verbose_name_plural': 'historical Regeln',
|
||||
'ordering': ('-history_date', '-history_id'),
|
||||
'get_latest_by': ('history_date', 'history_id'),
|
||||
},
|
||||
bases=(simple_history.models.HistoricalChanges, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HistoricalSearchSubscription',
|
||||
fields=[
|
||||
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
|
||||
('sex', models.CharField(choices=[('F', 'Weiblich'), ('M', 'Männlich'), ('M_N', 'Männlich, kastriert'), ('F_N', 'Weiblich Kastriert'), ('I', 'Intergeschlechtlich'), ('A', 'Alle')], max_length=20, verbose_name='Geschlecht')),
|
||||
('max_distance', models.IntegerField(choices=[(20, '20 km'), (50, '50 km'), (100, '100 km'), (200, '200 km'), (500, '500 km')], null=True)),
|
||||
('updated_at', models.DateTimeField(blank=True, editable=False, verbose_name='Zuletzt geändert am')),
|
||||
('created_at', models.DateTimeField(blank=True, editable=False, verbose_name='Erstellt am')),
|
||||
('history_id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('history_date', models.DateTimeField(db_index=True)),
|
||||
('history_change_reason', models.CharField(max_length=100, null=True)),
|
||||
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
|
||||
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||
('location', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.location')),
|
||||
('owner', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'historical Abonnierte Suche',
|
||||
'verbose_name_plural': 'historical Abonnierte Suchen',
|
||||
'ordering': ('-history_date', '-history_id'),
|
||||
'get_latest_by': ('history_date', 'history_id'),
|
||||
},
|
||||
bases=(simple_history.models.HistoricalChanges, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HistoricalSocialMediaPost',
|
||||
fields=[
|
||||
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
|
||||
('created_at', models.DateField(default=django.utils.timezone.now, verbose_name='Erstellt am')),
|
||||
('platform', models.CharField(choices=[('fediverse', 'Fediverse')], max_length=255, verbose_name='Social Media Platform')),
|
||||
('url', models.URLField(verbose_name='URL')),
|
||||
('history_id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('history_date', models.DateTimeField(db_index=True)),
|
||||
('history_change_reason', models.CharField(max_length=100, null=True)),
|
||||
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
|
||||
('adoption_notice', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.adoptionnotice', verbose_name='Vermittlung')),
|
||||
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'historical social media post',
|
||||
'verbose_name_plural': 'historical social media posts',
|
||||
'ordering': ('-history_date', '-history_id'),
|
||||
'get_latest_by': ('history_date', 'history_id'),
|
||||
},
|
||||
bases=(simple_history.models.HistoricalChanges, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HistoricalSubscriptions',
|
||||
fields=[
|
||||
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(blank=True, editable=False)),
|
||||
('updated_at', models.DateTimeField(blank=True, editable=False)),
|
||||
('history_id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('history_date', models.DateTimeField(db_index=True)),
|
||||
('history_change_reason', models.CharField(max_length=100, null=True)),
|
||||
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
|
||||
('adoption_notice', models.ForeignKey(blank=True, db_constraint=False, help_text='Vermittlung die abonniert wurde', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.adoptionnotice', verbose_name='Vermittlung')),
|
||||
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||
('owner', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Nutzer*in')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'historical Abonnement',
|
||||
'verbose_name_plural': 'historical Abonnements',
|
||||
'ordering': ('-history_date', '-history_id'),
|
||||
'get_latest_by': ('history_date', 'history_id'),
|
||||
},
|
||||
bases=(simple_history.models.HistoricalChanges, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HistoricalText',
|
||||
fields=[
|
||||
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=100, verbose_name='Titel')),
|
||||
('content', models.TextField(verbose_name='Inhalt')),
|
||||
('text_code', models.CharField(blank=True, max_length=24, verbose_name='Text code')),
|
||||
('history_id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('history_date', models.DateTimeField(db_index=True)),
|
||||
('history_change_reason', models.CharField(max_length=100, null=True)),
|
||||
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
|
||||
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||
('language', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.language', verbose_name='Sprache')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'historical Text',
|
||||
'verbose_name_plural': 'historical Texte',
|
||||
'ordering': ('-history_date', '-history_id'),
|
||||
'get_latest_by': ('history_date', 'history_id'),
|
||||
},
|
||||
bases=(simple_history.models.HistoricalChanges, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HistoricalUser',
|
||||
fields=[
|
||||
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
|
||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||
('username', models.CharField(db_index=True, error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
|
||||
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
|
||||
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
|
||||
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
|
||||
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||
('trust_level', models.IntegerField(choices=[(1, 'Member'), (2, 'Coordinator'), (3, 'Moderator'), (4, 'Admin')], default=1)),
|
||||
('updated_at', models.DateTimeField(blank=True, editable=False)),
|
||||
('reason_for_signup', models.TextField(help_text="Wir würden gerne wissen warum du dich registrierst, ob du dich z.B. Tiere eines bestimmten Tierheim einstellen willst 'nur mal gucken' willst. Beides ist toll! Wenn du für ein Tierheim/eine Pflegestelle arbeitest kontaktieren wir dich ggf. um dir erweiterte Rechte zu geben.", verbose_name='Grund für die Registrierung')),
|
||||
('mod_notes', models.TextField(blank=True, null=True, verbose_name='Moderationsnotizen')),
|
||||
('email_notifications', models.BooleanField(default=True, verbose_name='Benachrichtigung per E-Mail')),
|
||||
('history_id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('history_date', models.DateTimeField(db_index=True)),
|
||||
('history_change_reason', models.CharField(max_length=100, null=True)),
|
||||
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
|
||||
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||
('organization_affiliation', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.rescueorganization', verbose_name='Organisation')),
|
||||
('preferred_language', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.language', verbose_name='Bevorzugte Sprache')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'historical Nutzer*in',
|
||||
'verbose_name_plural': 'historical Nutzer*innen',
|
||||
'ordering': ('-history_date', '-history_id'),
|
||||
'get_latest_by': ('history_date', 'history_id'),
|
||||
},
|
||||
bases=(simple_history.models.HistoricalChanges, models.Model),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-29 09:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0071_historicaladoptionnotice_historicalanimal_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='adoptionnotice',
|
||||
name='adoption_notice_status',
|
||||
field=models.TextField(choices=[('active_searching', 'Searching'), ('active_interested', 'Interested'), ('awaiting_action_waiting_for_review', 'Waiting for review'), ('awaiting_action_needs_additional_info', 'Needs additional info'), ('awaiting_action_unchecked', 'Unchecked'), ('closed_successfully', 'Erfolgreich vermittelt'), ('closed_animal_died', 'Tier gestorben'), ('closed_for_other_adoption_notice', 'Vermittlung wurde zugunsten einer anderen geschlossen.'), ('closed_not_open_for_adoption_anymore', 'Tier(e) stehen nicht mehr zur Vermittlung bereit.'), ('closed_link_to_more_info_not_reachable', 'Der Link zu weiteren Informationen ist nicht mehr erreichbar.'), ('closed_other', 'Anderes'), ('disabled_against_the_rules', 'Against the rules'), ('disabled_other', 'Other (disabled)')], max_length=64, verbose_name='Status'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='historicaladoptionnotice',
|
||||
name='adoption_notice_status',
|
||||
field=models.TextField(choices=[('active_searching', 'Searching'), ('active_interested', 'Interested'), ('awaiting_action_waiting_for_review', 'Waiting for review'), ('awaiting_action_needs_additional_info', 'Needs additional info'), ('awaiting_action_unchecked', 'Unchecked'), ('closed_successfully', 'Erfolgreich vermittelt'), ('closed_animal_died', 'Tier gestorben'), ('closed_for_other_adoption_notice', 'Vermittlung wurde zugunsten einer anderen geschlossen.'), ('closed_not_open_for_adoption_anymore', 'Tier(e) stehen nicht mehr zur Vermittlung bereit.'), ('closed_link_to_more_info_not_reachable', 'Der Link zu weiteren Informationen ist nicht mehr erreichbar.'), ('closed_other', 'Anderes'), ('disabled_against_the_rules', 'Against the rules'), ('disabled_other', 'Other (disabled)')], max_length=64, verbose_name='Status'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
import logging
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def migrate_status(apps, schema_editor):
|
||||
# We can't import the model directly as it may be a newer
|
||||
# version than this migration expects. We use the historical version.
|
||||
AdoptionNotice = apps.get_model("fellchensammlung", "AdoptionNotice")
|
||||
for adoption_notice in AdoptionNotice.objects.filter(
|
||||
adoption_notice_status__in=("closed_successful_without_notfellchen", "closed_successful_with_notfellchen")):
|
||||
adoption_notice.adoption_notice_status = "closed_successfully"
|
||||
adoption_notice.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('fellchensammlung', '0072_alter_adoptionnotice_adoption_notice_status_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(migrate_status),
|
||||
]
|
||||
@@ -0,0 +1,104 @@
|
||||
# Generated by Django 5.2.8 on 2026-01-20 21:09
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("fellchensammlung", "0073_adoption_notice_status_successful"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="animal",
|
||||
name="date_of_birth",
|
||||
field=models.DateField(blank=True, null=True, verbose_name="Geburtsdatum"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="historicalanimal",
|
||||
name="date_of_birth",
|
||||
field=models.DateField(blank=True, null=True, verbose_name="Geburtsdatum"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="historicalrescueorganization",
|
||||
name="external_object_identifier",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
help_text="Id des Objekts in der externen Datenbank (kann leer gelassen werden)",
|
||||
max_length=200,
|
||||
null=True,
|
||||
verbose_name="External Object Identifier",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="historicalrescueorganization",
|
||||
name="external_source_identifier",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[("OSM", "Open Street Map")],
|
||||
help_text="Name der Datenbank aus der die Tierschutzorganisation importiert wurde (kann leer gelassen werden)",
|
||||
max_length=200,
|
||||
null=True,
|
||||
verbose_name="External Source Identifier",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="historicalrescueorganization",
|
||||
name="parent_org",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||
related_name="+",
|
||||
to="fellchensammlung.rescueorganization",
|
||||
verbose_name="Übergeordnete Organisation",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="rescueorganization",
|
||||
name="external_object_identifier",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
help_text="Id des Objekts in der externen Datenbank (kann leer gelassen werden)",
|
||||
max_length=200,
|
||||
null=True,
|
||||
verbose_name="External Object Identifier",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="rescueorganization",
|
||||
name="external_source_identifier",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[("OSM", "Open Street Map")],
|
||||
help_text="Name der Datenbank aus der die Tierschutzorganisation importiert wurde (kann leer gelassen werden)",
|
||||
max_length=200,
|
||||
null=True,
|
||||
verbose_name="External Source Identifier",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="rescueorganization",
|
||||
name="parent_org",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to="fellchensammlung.rescueorganization",
|
||||
verbose_name="Übergeordnete Organisation",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="speciesspecificurl",
|
||||
name="rescue_organization",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="species_specific_urls",
|
||||
to="fellchensammlung.rescueorganization",
|
||||
verbose_name="Tierschutzorganisation",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -7,13 +7,15 @@ from django.contrib.auth.models import Group
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.core.exceptions import ValidationError
|
||||
import base64
|
||||
from simple_history.models import HistoricalRecords
|
||||
|
||||
from .tools import misc, geo
|
||||
from notfellchen.settings import MEDIA_URL, base_url
|
||||
from .tools.geo import LocationProxy, Position
|
||||
from .tools.misc import time_since_as_hr_string
|
||||
from .tools.model_helpers import NotificationTypeChoices, AdoptionNoticeStatusChoices, AdoptionProcess, \
|
||||
AdoptionNoticeStatusChoicesDescriptions
|
||||
AdoptionNoticeStatusChoicesDescriptions, RegularCheckStatusChoices, reason_for_signup_label, \
|
||||
reason_for_signup_help_text
|
||||
from .tools.model_helpers import ndm as NotificationDisplayMapping
|
||||
|
||||
|
||||
@@ -120,11 +122,11 @@ class ExternalSourceChoices(models.TextChoices):
|
||||
|
||||
|
||||
class AllowUseOfMaterialsChices(models.TextChoices):
|
||||
USE_MATERIALS_ALLOWED = "allowed", _("Usage allowed")
|
||||
USE_MATERIALS_REQUESTED = "requested", _("Usage requested")
|
||||
USE_MATERIALS_DENIED = "denied", _("Usage denied")
|
||||
USE_MATERIALS_OTHER = "other", _("It's complicated")
|
||||
USE_MATERIALS_NOT_ASKED = "not_asked", _("Not asked")
|
||||
USE_MATERIALS_ALLOWED = "allowed", _("Nutzung erlaubt")
|
||||
USE_MATERIALS_REQUESTED = "requested", _("Nutzung angefragt")
|
||||
USE_MATERIALS_DENIED = "denied", _("Nutzung verweigert")
|
||||
USE_MATERIALS_OTHER = "other", _("Es ist kompliziert")
|
||||
USE_MATERIALS_NOT_ASKED = "not_asked", _("Nutzung noch nicht angefragt")
|
||||
|
||||
|
||||
class Species(models.Model):
|
||||
@@ -165,21 +167,33 @@ class RescueOrganization(models.Model):
|
||||
internal_comment = models.TextField(verbose_name=_("Interner Kommentar"), null=True, blank=True, )
|
||||
description = models.TextField(null=True, blank=True, verbose_name=_('Beschreibung')) # Markdown allowed
|
||||
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,
|
||||
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'),
|
||||
help_text=_("Organisation von der manuellen Überprüfung ausschließen, "
|
||||
"z.B. weil Tiere nicht online geführt werden"))
|
||||
regular_check_status = models.CharField(max_length=30, choices=RegularCheckStatusChoices.choices,
|
||||
default=RegularCheckStatusChoices.REGULAR_CHECK,
|
||||
verbose_name=_('Status der regelmäßigen Prüfung'),
|
||||
help_text=_(
|
||||
"Organisationen können, durch ändern dieser Einstellung, von der "
|
||||
"regelmäßigen Prüfung ausgeschlossen werden."))
|
||||
ongoing_communication = models.BooleanField(default=False, verbose_name=_('In aktiver Kommunikation'),
|
||||
help_text=_(
|
||||
"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
|
||||
specializations = models.ManyToManyField(Species, blank=True)
|
||||
twenty_id = models.UUIDField(verbose_name=_("Twenty-ID"), null=True, blank=True,
|
||||
help_text=_("ID der der Organisation in Twenty"))
|
||||
history = HistoricalRecords()
|
||||
|
||||
class Meta:
|
||||
unique_together = ('external_object_identifier', 'external_source_identifier',)
|
||||
@@ -243,6 +257,7 @@ class RescueOrganization(models.Model):
|
||||
|
||||
def set_checked(self):
|
||||
self.last_checked = timezone.now()
|
||||
self._change_reason = 'Organization checked'
|
||||
self.save()
|
||||
|
||||
@property
|
||||
@@ -261,10 +276,6 @@ class RescueOrganization(models.Model):
|
||||
"""
|
||||
return self.instagram or self.facebook or self.website or self.phone_number or self.email or self.fediverse_profile
|
||||
|
||||
def set_exclusion_from_checks(self):
|
||||
self.exclude_from_check = True
|
||||
self.save()
|
||||
|
||||
@property
|
||||
def child_organizations(self):
|
||||
return RescueOrganization.objects.filter(parent_org=self)
|
||||
@@ -305,9 +316,10 @@ class User(AbstractUser):
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
organization_affiliation = models.ForeignKey(RescueOrganization, on_delete=models.PROTECT, null=True, blank=True,
|
||||
verbose_name=_('Organisation'))
|
||||
reason_for_signup = models.TextField(verbose_name=_("Grund für die Registrierung"), help_text=_(
|
||||
"Wir würden gerne wissen warum du dich registriertst, ob du dich z.B. Tiere eines bestimmten Tierheim einstellen willst 'nur mal gucken' willst. Beides ist toll! Wenn du für ein Tierheim/eine Pflegestelle arbeitest kontaktieren wir dich ggf. um dir erweiterte Rechte zu geben."))
|
||||
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)
|
||||
history = HistoricalRecords()
|
||||
REQUIRED_FIELDS = ["reason_for_signup", "email"]
|
||||
|
||||
class Meta:
|
||||
@@ -403,6 +415,7 @@ class AdoptionNotice(models.Model):
|
||||
adoption_process = models.TextField(null=True, blank=True,
|
||||
max_length=64, verbose_name=_('Adoptionsprozess'),
|
||||
choices=AdoptionProcess)
|
||||
history = HistoricalRecords()
|
||||
|
||||
@property
|
||||
def animals(self):
|
||||
@@ -419,7 +432,7 @@ class AdoptionNotice(models.Model):
|
||||
def num_per_sex(self):
|
||||
num_per_sex = dict()
|
||||
for sex in SexChoices:
|
||||
num_per_sex[sex] = self.animals.filter(sex=sex).count()
|
||||
num_per_sex[sex] = len([animal for animal in self.animals if animal.sex == sex])
|
||||
return num_per_sex
|
||||
|
||||
@property
|
||||
@@ -509,6 +522,7 @@ class AdoptionNotice(models.Model):
|
||||
photos.extend(animal.photos.all())
|
||||
if len(photos) > 0:
|
||||
return photos
|
||||
return None
|
||||
|
||||
def get_photo(self):
|
||||
"""
|
||||
@@ -538,6 +552,13 @@ class AdoptionNotice(models.Model):
|
||||
def _values_of(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
|
||||
def is_active(self):
|
||||
return self.adoption_notice_status in self._values_of(AdoptionNoticeStatusChoices.Active.choices)
|
||||
@@ -554,6 +575,19 @@ class AdoptionNotice(models.Model):
|
||||
def is_awaiting_action(self):
|
||||
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
|
||||
def status_description(self):
|
||||
return AdoptionNoticeStatusChoicesDescriptions.mapping[self.adoption_notice_status]
|
||||
@@ -603,7 +637,7 @@ class Animal(models.Model):
|
||||
verbose_name = _('Tier')
|
||||
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'))
|
||||
description = models.TextField(null=True, blank=True, verbose_name=_('Beschreibung'))
|
||||
species = models.ForeignKey(Species, on_delete=models.PROTECT, verbose_name=_("Tierart"))
|
||||
@@ -617,18 +651,25 @@ class Animal(models.Model):
|
||||
owner = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
history = HistoricalRecords()
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name}"
|
||||
|
||||
@property
|
||||
def age(self):
|
||||
if self.date_of_birth:
|
||||
return timezone.now().today().date() - self.date_of_birth
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def hr_age(self):
|
||||
"""Returns a human-readable age based on the date of birth."""
|
||||
if self.date_of_birth:
|
||||
return misc.age_as_hr_string(self.age)
|
||||
else:
|
||||
return _("Unbekannt")
|
||||
|
||||
def get_photo(self):
|
||||
"""
|
||||
@@ -680,6 +721,7 @@ class SearchSubscription(models.Model):
|
||||
max_distance = models.IntegerField(choices=DistanceChoices.choices, null=True)
|
||||
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"))
|
||||
history = HistoricalRecords()
|
||||
|
||||
def __str__(self):
|
||||
if self.location and self.max_distance:
|
||||
@@ -710,6 +752,7 @@ class Rule(models.Model):
|
||||
"Identifikator haben"))
|
||||
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"))
|
||||
history = HistoricalRecords()
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
@@ -735,6 +778,7 @@ class Report(models.Model):
|
||||
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"))
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Erstellt am"))
|
||||
history = HistoricalRecords()
|
||||
|
||||
def __str__(self):
|
||||
return f"[{self.status}]: {self.user_comment:.20}"
|
||||
@@ -821,6 +865,7 @@ class ModerationAction(models.Model):
|
||||
# Only visible to moderator
|
||||
private_comment = models.TextField(blank=True)
|
||||
report = models.ForeignKey(Report, on_delete=models.CASCADE)
|
||||
history = HistoricalRecords()
|
||||
|
||||
def __str__(self):
|
||||
return f"[{self.action}]: {self.public_comment}"
|
||||
@@ -842,6 +887,7 @@ class Text(models.Model):
|
||||
content = models.TextField(verbose_name="Inhalt")
|
||||
language = models.ForeignKey(Language, verbose_name="Sprache", on_delete=models.PROTECT)
|
||||
text_code = models.CharField(max_length=24, verbose_name="Text code", blank=True)
|
||||
history = HistoricalRecords()
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Text"
|
||||
@@ -885,6 +931,7 @@ class Announcement(Text):
|
||||
INFO: "info",
|
||||
}
|
||||
type = models.CharField(choices=TYPES, max_length=100, default=INFO)
|
||||
history = HistoricalRecords()
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
@@ -931,6 +978,7 @@ class Comment(models.Model):
|
||||
adoption_notice = models.ForeignKey(AdoptionNotice, on_delete=models.CASCADE, verbose_name=_('Vermittlung'))
|
||||
text = models.TextField(verbose_name="Inhalt")
|
||||
reply_to = models.ForeignKey("self", verbose_name="Antwort auf", blank=True, null=True, on_delete=models.CASCADE)
|
||||
history = HistoricalRecords()
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user} at {self.created_at.strftime('%H:%M %d.%m.%y')}: {self.text:.10}"
|
||||
@@ -1002,6 +1050,7 @@ class Subscriptions(models.Model):
|
||||
help_text=_("Vermittlung die abonniert wurde"))
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
history = HistoricalRecords()
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.owner} - {self.adoption_notice}"
|
||||
@@ -1048,8 +1097,10 @@ class SpeciesSpecificURL(models.Model):
|
||||
verbose_name_plural = _("Tierartspezifische URLs")
|
||||
|
||||
species = models.ForeignKey(Species, on_delete=models.CASCADE, verbose_name=_("Tierart"))
|
||||
rescue_organization = models.ForeignKey(RescueOrganization, on_delete=models.CASCADE,
|
||||
verbose_name=_("Tierschutzorganisation"))
|
||||
rescue_organization = models.ForeignKey(RescueOrganization,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name=_("Tierschutzorganisation"),
|
||||
related_name="species_specific_urls")
|
||||
url = models.URLField(verbose_name=_("Tierartspezifische URL"))
|
||||
|
||||
|
||||
@@ -1063,6 +1114,7 @@ class SocialMediaPost(models.Model):
|
||||
choices=PlatformChoices.choices)
|
||||
adoption_notice = models.ForeignKey(AdoptionNotice, on_delete=models.CASCADE, verbose_name=_('Vermittlung'))
|
||||
url = models.URLField(verbose_name=_("URL"))
|
||||
history = HistoricalRecords()
|
||||
|
||||
@staticmethod
|
||||
def get_an_to_post():
|
||||
|
||||
@@ -22,12 +22,24 @@ $confirm: hsl(133deg, 100%, calc(41% + 0%));
|
||||
background-color: $grey-light !important;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.navbar-burger {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background-color: $grey-dark;
|
||||
}
|
||||
a.card-footer-item.is-danger {
|
||||
color: black;
|
||||
div.card-footer-item.is-danger {
|
||||
background-color: #5a212d;
|
||||
}
|
||||
div.card-footer-item.is-warning {
|
||||
background-color: #523e13;
|
||||
}
|
||||
div.card-footer-item.is-confirm {
|
||||
background-color: #00420f;
|
||||
}
|
||||
|
||||
.tag {
|
||||
color: $grey-dark;
|
||||
background-color: $grey-light;
|
||||
@@ -245,6 +257,34 @@ IMAGES
|
||||
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
|
||||
@@ -322,7 +362,6 @@ AN Cards
|
||||
}
|
||||
|
||||
|
||||
|
||||
.notification-container {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
|
||||
12
src/fellchensammlung/templates/allauth/elements/badge.html
Normal file
12
src/fellchensammlung/templates/allauth/elements/badge.html
Normal file
@@ -0,0 +1,12 @@
|
||||
{% load allauth %}
|
||||
{% setvar variant %}
|
||||
{% if "primary" in attrs.tags %}
|
||||
is-success
|
||||
{% elif "secondary" in attrs.tags %}
|
||||
is-success is-light
|
||||
{% endif %}
|
||||
{% endsetvar %}
|
||||
<span class="tag{% if variant %} {{ variant }}{% endif %}" {% if attrs.title %}title="{{ attrs.title }}"{% endif %}>
|
||||
{% slot %}
|
||||
{% endslot %}
|
||||
</span>
|
||||
15
src/fellchensammlung/templates/allauth/elements/button.html
Normal file
15
src/fellchensammlung/templates/allauth/elements/button.html
Normal file
@@ -0,0 +1,15 @@
|
||||
{% load allauth %}
|
||||
{% comment %} djlint:off {% endcomment %}
|
||||
<div class="control">
|
||||
<{% if attrs.href %}a href="{{ attrs.href }}"{% else %}button{% endif %}
|
||||
class="button is-primary"
|
||||
{% if attrs.form %}form="{{ attrs.form }}"{% endif %}
|
||||
{% if attrs.id %}id="{{ attrs.id }}"{% endif %}
|
||||
{% if attrs.name %}name="{{ attrs.name }}"{% endif %}
|
||||
{% if attrs.value %}value="{{ attrs.value }}"{% endif %}
|
||||
{% if attrs.type %}type="{{ attrs.type }}"{% endif %}
|
||||
>
|
||||
{% slot %}
|
||||
{% endslot %}
|
||||
</{% if attrs.href %}a{% else %}button{% endif %}>
|
||||
</div>
|
||||
@@ -0,0 +1,5 @@
|
||||
{% load allauth %}
|
||||
<div class="field is-grouped">
|
||||
{% slot %}
|
||||
{% endslot %}
|
||||
</div>
|
||||
50
src/fellchensammlung/templates/allauth/elements/field.html
Normal file
50
src/fellchensammlung/templates/allauth/elements/field.html
Normal file
@@ -0,0 +1,50 @@
|
||||
{% load allauth %}
|
||||
<div class="field">
|
||||
|
||||
{% if attrs.type == "textarea" %}
|
||||
<label class="label" for="{{ attrs.id }}">
|
||||
{% slot label %}
|
||||
{% endslot %}
|
||||
</label>
|
||||
<textarea class="textarea"
|
||||
{% if attrs.required %}required{% endif %}
|
||||
{% if attrs.rows %}rows="{{ attrs.rows }}"{% endif %}
|
||||
{% if attrs.disabled %}disabled{% endif %}
|
||||
{% if attrs.readonly %}readonly{% endif %}
|
||||
{% if attrs.checked %}checked{% endif %}
|
||||
{% if attrs.name %}name="{{ attrs.name }}"{% endif %}
|
||||
{% if attrs.id %}id="{{ attrs.id }}"{% endif %}
|
||||
{% if attrs.placeholder %}placeholder="{{ attrs.placeholder }}"{% endif %}>{% slot value %}{% endslot %}</textarea>
|
||||
{% else %}
|
||||
{% if attrs.type != "checkbox" and attrs.type != "radio" %}
|
||||
<label class="label" for="{{ attrs.id }}">
|
||||
{% slot label %}
|
||||
{% endslot %}
|
||||
</label>
|
||||
{% endif %}
|
||||
<input {% if attrs.type != "checkbox" and attrs.type != "radio" %}class="input"{% endif %}
|
||||
{% if attrs.required %}required{% endif %}
|
||||
{% if attrs.disabled %}disabled{% endif %}
|
||||
{% if attrs.readonly %}readonly{% endif %}
|
||||
{% if attrs.checked %}checked{% endif %}
|
||||
{% if attrs.name %}name="{{ attrs.name }}"{% endif %}
|
||||
{% if attrs.id %}id="{{ attrs.id }}"{% endif %}
|
||||
{% if attrs.placeholder %}placeholder="{{ attrs.placeholder }}"{% endif %}
|
||||
{% if attrs.autocomplete %}autocomplete="{{ attrs.autocomplete }}"{% endif %}
|
||||
{% if attrs.value is not None %}value="{{ attrs.value }}"{% endif %}
|
||||
type="{{ attrs.type }}">
|
||||
{% if attrs.type == "checkbox" or attrs.type == "radio" %}
|
||||
<label for="{{ attrs.id }}">
|
||||
{% slot label %}
|
||||
{% endslot %}
|
||||
</label>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if slots.help_text %}
|
||||
<p class="help is-danger">
|
||||
{% slot help_text %}
|
||||
{% endslot %}
|
||||
</p>
|
||||
{% endif %}
|
||||
<p class="help is-danger">{{ attrs.errors }}</p>
|
||||
</div>
|
||||
@@ -0,0 +1 @@
|
||||
{{ attrs.form }}
|
||||
12
src/fellchensammlung/templates/allauth/elements/form.html
Normal file
12
src/fellchensammlung/templates/allauth/elements/form.html
Normal file
@@ -0,0 +1,12 @@
|
||||
{% load allauth %}
|
||||
<div class="block">
|
||||
<form method="{{ attrs.method }}"
|
||||
{% if attrs.action %}action="{{ attrs.action }}"{% endif %}>
|
||||
{% slot body %}
|
||||
{% endslot %}
|
||||
<div class="field is-grouped is-grouped-multiline">
|
||||
{% slot actions %}
|
||||
{% endslot %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
1
src/fellchensammlung/templates/allauth/elements/h1.html
Normal file
1
src/fellchensammlung/templates/allauth/elements/h1.html
Normal file
@@ -0,0 +1 @@
|
||||
{% comment %} djlint:off {% endcomment %}{% load allauth %}<h1 class="title is-1">{% slot %}{% endslot %}</h1>
|
||||
1
src/fellchensammlung/templates/allauth/elements/h2.html
Normal file
1
src/fellchensammlung/templates/allauth/elements/h2.html
Normal file
@@ -0,0 +1 @@
|
||||
{% comment %} djlint:off {% endcomment %}{% load allauth %}<h2 class="title is-2">{% slot %}{% endslot %}</h2>
|
||||
1
src/fellchensammlung/templates/allauth/elements/p.html
Normal file
1
src/fellchensammlung/templates/allauth/elements/p.html
Normal file
@@ -0,0 +1 @@
|
||||
{% comment %} djlint:off {% endcomment %}{% load allauth %}<p class="content">{% slot %}{% endslot %}</p>
|
||||
18
src/fellchensammlung/templates/allauth/elements/panel.html
Normal file
18
src/fellchensammlung/templates/allauth/elements/panel.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{% load allauth %}
|
||||
<section class="block">
|
||||
<h2 class="title is-2">
|
||||
{% slot title %}
|
||||
{% endslot %}
|
||||
</h2>
|
||||
{% slot body %}
|
||||
{% endslot %}
|
||||
{% if slots.actions %}
|
||||
<div class="field is-grouped is-grouped-multiline">
|
||||
{% for action in slots.actions %}
|
||||
<div class="control">
|
||||
{{ action }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</section>
|
||||
1
src/fellchensammlung/templates/allauth/layouts/base.html
Normal file
1
src/fellchensammlung/templates/allauth/layouts/base.html
Normal file
@@ -0,0 +1 @@
|
||||
{% extends "fellchensammlung/base.html" %}
|
||||
@@ -33,6 +33,11 @@
|
||||
<i class="fas fa-search fa-fw"></i> {% trans 'Suchen' %}
|
||||
</button>
|
||||
</form>
|
||||
<hr>
|
||||
<form method="post" autocomplete="off">
|
||||
{% csrf_token %}
|
||||
{{ org_name_search_form }}
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -69,4 +74,57 @@
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</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 %}
|
||||
|
||||
@@ -37,6 +37,26 @@
|
||||
{% block header %}
|
||||
{% include "fellchensammlung/header.html" %}
|
||||
{% endblock %}
|
||||
{% if profile %}
|
||||
<div class="profile">
|
||||
<table class="table is-bordered is-fullwidth is-hoverable is-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Timestamp</td>
|
||||
<td>Status</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for status in profile %}
|
||||
<tr>
|
||||
<td>{{ status.0 }}</td>
|
||||
<td>{{ status.1 }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="main-content">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
@@ -45,5 +65,8 @@
|
||||
{% block footer %}
|
||||
{% include "fellchensammlung/footer.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_body %}
|
||||
{% endblock extra_body %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -114,7 +114,17 @@
|
||||
<i class="fas fa-chart-line fa-fw"></i> {% trans 'Aufrufe' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<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"
|
||||
href="{% url adoption_notice_meta|admin_urlname:'change' adoption_notice.pk %}">
|
||||
<i class="fa-solid fa-tools fa-fw"></i> Admin interface
|
||||
@@ -164,6 +174,9 @@
|
||||
{% if adoption_notice.get_photos %}
|
||||
<div class="column block">
|
||||
<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="gallery">
|
||||
{% with photo=adoption_notice.get_photos.0 %}
|
||||
|
||||
@@ -19,59 +19,25 @@
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div class="block">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h1 class="card-header-title">{{ org.name }}</h1>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="block">
|
||||
<b><i class="fa-solid fa-location-dot"></i></b>
|
||||
{% if org.location %}
|
||||
{{ org.location }}
|
||||
{% else %}
|
||||
{{ org.location_string }}
|
||||
{% endif %}
|
||||
{% if org.description %}
|
||||
<div class="block content">
|
||||
<p>{{ org.description | render_markdown }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if org.specializations %}
|
||||
<div class="block">
|
||||
<h3 class="title is-5">{% translate 'Spezialisierung' %}</h3>
|
||||
<div class="content">
|
||||
<ul>
|
||||
{% for specialization in org.specializations.all %}
|
||||
<li>{{ specialization }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if org.parent_org %}
|
||||
<div class="block">
|
||||
<h3 class="title is-5">{% translate 'Übergeordnete Organisation' %}</h3>
|
||||
<p>
|
||||
<span>
|
||||
<i class="fa-solid fa-building fa-fw"
|
||||
aria-label="{% trans 'Tierschutzorganisation' %}"></i>
|
||||
<a href="{{ org.parent_org.get_absolute_url }}"> {{ org.parent_org }}</a>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% include "fellchensammlung/partials/rescue_orgs/partial-basic-info-card.html" %}
|
||||
</div>
|
||||
<div class="block">
|
||||
{% include "fellchensammlung/partials/partial-rescue-organization-contact.html" %}
|
||||
{% include "fellchensammlung/partials/rescue_orgs/partial-rescue-organization-contact.html" %}
|
||||
</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">
|
||||
<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
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="column">
|
||||
{% include "fellchensammlung/partials/partial-map.html" %}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{% extends "fellchensammlung/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load account %}
|
||||
|
||||
{% block title %}<title>{{ user.get_full_name }}</title>{% endblock %}
|
||||
{% block title %}<title>{% user_display user %}</title>{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
@@ -13,7 +14,7 @@
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<div class="level-item">
|
||||
<form class="" action="{% url 'logout' %}" method="post">
|
||||
<form class="" action="{% url 'account_logout' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<button class="button" type="submit">
|
||||
<i aria-hidden="true" class="fas fa-sign-out fa-fw"></i> Logout
|
||||
@@ -25,16 +26,34 @@
|
||||
|
||||
<div class="block">
|
||||
<h2 class="title is-2">{% trans 'Profil verwalten' %}</h2>
|
||||
<p><strong>{% translate "E-Mail" %}:</strong> {{ user.email }}</p>
|
||||
<div class="">
|
||||
<p>
|
||||
<a class="button is-warning" href="{% url 'password_change' %}">{% translate "Change password" %}</a>
|
||||
<a class="button is-info" href="{% url 'user-me-export' %}">{% translate "Daten exportieren" %}</a>
|
||||
</p>
|
||||
<div class="block"><strong>{% translate "E-Mail" %}:</strong> {{ user.email }}</div>
|
||||
{% if user.id is request.user.id %}
|
||||
<div class="block">
|
||||
<div class="field is-grouped is-grouped-multiline">
|
||||
<div class="control">
|
||||
<a class="button is-warning"
|
||||
href="{% url 'account_change_password' %}">{% translate "Passwort ändern" %}</a>
|
||||
</div>
|
||||
<div class="control">
|
||||
<a class="button is-warning"
|
||||
href="{% url 'account_email' %}">
|
||||
{% translate "E-Mail Adresse ändern" %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="control">
|
||||
<a class="button is-warning"
|
||||
href="{% url 'mfa_index' %}">
|
||||
{% translate "2-Faktor Authentifizierung" %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="control">
|
||||
<a class="button is-info" href="{% url 'user-me-export' %}">
|
||||
{% translate "Daten exportieren" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if user.id is request.user.id %}
|
||||
<div class="block">
|
||||
<h2 class="title is-2">{% trans 'Einstellungen' %}</h2>
|
||||
<form class="block" action="" method="POST">
|
||||
@@ -79,6 +98,27 @@
|
||||
</details>
|
||||
|
||||
</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>
|
||||
{% include "fellchensammlung/lists/list-notifications.html" %}
|
||||
@@ -86,8 +126,7 @@
|
||||
<h2 class="title is-2">{% translate 'Abonnierte Suchen' %}</h2>
|
||||
{% 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" %}
|
||||
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -7,9 +7,14 @@
|
||||
{% block content %}
|
||||
<h1 class="title is-1">403 Forbidden</h1>
|
||||
<p>
|
||||
{% if error_message %}
|
||||
{{ error_message }}
|
||||
{% else %}
|
||||
{% 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!
|
||||
{% endblocktranslate %}
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endblock %}
|
||||
@@ -2,7 +2,7 @@
|
||||
{% load i18n %}
|
||||
{% load custom_tags %}
|
||||
|
||||
{% block title %}<title>{% translate "403 Forbidden" %}</title>{% endblock %}
|
||||
{% block title %}<title>{% translate "404 Forbidden" %}</title>{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="title is-1">404 Not Found</h1>
|
||||
|
||||
@@ -102,6 +102,10 @@
|
||||
<a class="nav-link " href="{% url "modtools" %}">
|
||||
{% translate 'Moderationstools' %}
|
||||
</a>
|
||||
<br>
|
||||
<a class="nav-link " href="{% url "rescue-organization-create" %}">
|
||||
{% translate 'Tierschutzorganisation hinzufügen' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
<br/>
|
||||
{% 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,35 @@
|
||||
{% extends "fellchensammlung/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load widget_tweaks %}
|
||||
{% load admin_urls %}
|
||||
|
||||
{% block title %}
|
||||
<title>Organisation {{ org }} von regelmäßiger Prüfung ausschließen</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="title is-1">Organisation {{ org }} von regelmäßiger Prüfung ausschließen</h1>
|
||||
<div class="columns block">
|
||||
<div class="column">
|
||||
{% include "fellchensammlung/partials/rescue_orgs/partial-basic-info-card.html" %}
|
||||
</div>
|
||||
<div class="column">
|
||||
{% include "fellchensammlung/partials/rescue_orgs/partial-rescue-organization-contact.html" %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="block">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
|
||||
<input class="button is-primary" type="submit" name="delete"
|
||||
value="{% translate "Aktualisieren" %}">
|
||||
<a class="button" href="{% url 'organization-check' %}">{% translate "Zurück" %}</a>
|
||||
</form>
|
||||
</div>
|
||||
<div class="block">
|
||||
<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
|
||||
</a>
|
||||
</div>
|
||||
{% 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 %}
|
||||
@@ -27,7 +27,7 @@
|
||||
{{ field|add_class:"input" }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="help">
|
||||
<div class="help content">
|
||||
{{ field.help_text }}
|
||||
</div>
|
||||
<div class="help is-danger">
|
||||
|
||||
@@ -49,10 +49,10 @@
|
||||
{% else %}
|
||||
<div class="navbar-item">
|
||||
<div class="buttons">
|
||||
<a class="button is-primary" href="{% url "django_registration_register" %}">
|
||||
<a class="button is-primary" href="{% url "account_signup" %}">
|
||||
<strong>{% translate "Registrieren" %}</strong>
|
||||
</a>
|
||||
<a class="button is-light" href="{% url "login" %}">
|
||||
<a class="button is-light" href="{% url "account_login" %}">
|
||||
<strong>{% translate "Login" %}</strong>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 54 KiB |
@@ -3,7 +3,7 @@
|
||||
{% if rescue_organizations %}
|
||||
{% for rescue_organization in rescue_organizations %}
|
||||
<div class="cell">
|
||||
{% include "fellchensammlung/partials/partial-rescue-organization.html" %}
|
||||
{% include "fellchensammlung/partials/rescue_orgs/partial-rescue-organization.html" %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
|
||||
@@ -6,21 +6,7 @@
|
||||
<h1 class="title is-1">{% translate 'Vermittlung deaktivieren' %}</h1>
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<div class="field">
|
||||
<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>
|
||||
{{ form }}
|
||||
<input class="button is-warning" type="submit" value="{% translate "Vermittlung deaktivieren" %}">
|
||||
</form>
|
||||
{% 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 class="block">
|
||||
{% 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">
|
||||
<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>
|
||||
{% include "fellchensammlung/partials/social_media/post-to-fedi.html" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
{% translate 'Adoptionsprozess' %}
|
||||
</h2>
|
||||
</div>
|
||||
{% if not adoption_notice.is_active %}
|
||||
<div class="cover">{{ adoption_notice.status_description_short }}</div>
|
||||
{% endif %}
|
||||
<div class="card-content">
|
||||
{% include adoption_process_template %}
|
||||
</div>
|
||||
|
||||
@@ -31,5 +31,14 @@
|
||||
{{ adoption_notice.status_description }}
|
||||
</div>
|
||||
</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 %}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% load i18n %}
|
||||
{% load custom_tags %}
|
||||
<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">
|
||||
{{ adoption_notice.name }}
|
||||
</div>
|
||||
@@ -17,25 +17,21 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<div class="card-footer-item is-confirm">
|
||||
<form method="post">
|
||||
<form class="card-footer-item is-confirm" method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden"
|
||||
name="adoption_notice_id"
|
||||
value="{{ adoption_notice.pk }}">
|
||||
<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>
|
||||
</div>
|
||||
<div class="card-footer-item is-warning">
|
||||
<form method="post">
|
||||
<form class="card-footer-item is-warning" method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden"
|
||||
name="adoption_notice_id"
|
||||
value="{{ adoption_notice.pk }}">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,46 @@
|
||||
{% load i18n %}
|
||||
{% load custom_tags %}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h1 class="card-header-title">{{ org.name }}</h1>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="block">
|
||||
<b><i class="fa-solid fa-location-dot"></i></b>
|
||||
{% if org.location %}
|
||||
{{ org.location }}
|
||||
{% else %}
|
||||
{{ org.location_string }}
|
||||
{% endif %}
|
||||
{% if org.description %}
|
||||
<div class="block content">
|
||||
<p>{{ org.description | render_markdown }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if org.specializations %}
|
||||
<div class="block">
|
||||
<h3 class="title is-5">{% translate 'Spezialisierung' %}</h3>
|
||||
<div class="content">
|
||||
<ul>
|
||||
{% for specialization in org.specializations.all %}
|
||||
<li>{{ specialization }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if org.parent_org %}
|
||||
<div class="block">
|
||||
<h3 class="title is-5">{% translate 'Übergeordnete Organisation' %}</h3>
|
||||
<p>
|
||||
<span>
|
||||
<i class="fa-solid fa-building fa-fw"
|
||||
aria-label="{% trans 'Tierschutzorganisation' %}"></i>
|
||||
<a href="{{ org.parent_org.get_absolute_url }}"> {{ org.parent_org }}</a>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -71,19 +71,26 @@
|
||||
value="{{ rescue_org.pk }}">
|
||||
<input type="hidden" name="action" value="checked">
|
||||
<button class="" type="submit">{% translate "Organisation geprüft" %}</button>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<div class="card-footer-item is-danger">
|
||||
<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="exclude">
|
||||
<button class="" type="submit">{% translate "Von Check exkludieren" %}</button>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
<div class="card-footer-item is-danger">
|
||||
<a href="{% url 'rescue-organization-exclude' rescue_organization_id=rescue_org.pk %}">{% translate "Von Check exkludieren" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,9 +1,10 @@
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
<div class="grid is-col-min-2">
|
||||
{% if adoption_notice.num_per_sex.F > 0 %}
|
||||
{% with num_per_sex=adoption_notice.num_per_sex %}
|
||||
{% if num_per_sex.F > 0 %}
|
||||
<span class="cell icon-text tag is-medium">
|
||||
<span class="has-text-weight-bold is-size-4">{{ adoption_notice.num_per_sex.F }} </span>
|
||||
<span class="has-text-weight-bold is-size-4">{{ num_per_sex.F }}</span>
|
||||
<span class="icon">
|
||||
<img class="icon" src="{% static 'fellchensammlung/img/sexes/Female.png' %}"
|
||||
alt="{% translate 'weibliche Tiere' %}">
|
||||
@@ -11,9 +12,9 @@
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{% if adoption_notice.num_per_sex.I > 0 %}
|
||||
{% if num_per_sex.I > 0 %}
|
||||
<span class="cell icon-text tag is-medium">
|
||||
<span class="has-text-weight-bold is-size-4">{{ adoption_notice.num_per_sex.I }}</span>
|
||||
<span class="has-text-weight-bold is-size-4">{{ num_per_sex.I }}</span>
|
||||
|
||||
<span class="icon">
|
||||
<img class="icon"
|
||||
@@ -22,18 +23,18 @@
|
||||
</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if adoption_notice.num_per_sex.M > 0 %}
|
||||
{% if num_per_sex.M > 0 %}
|
||||
<span class="cell icon-text tag is-medium">
|
||||
<span class="has-text-weight-bold is-size-4">{{ adoption_notice.num_per_sex.M }}</span>
|
||||
<span class="has-text-weight-bold is-size-4">{{ num_per_sex.M }}</span>
|
||||
<span class="icon">
|
||||
<img class="icon" src="{% static 'fellchensammlung/img/sexes/Male.png' %}"
|
||||
alt="{% translate 'männliche Tiere' %}">
|
||||
</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if adoption_notice.num_per_sex.M_N > 0 %}
|
||||
{% if num_per_sex.M_N > 0 %}
|
||||
<span class="cell icon-text tag is-medium">
|
||||
<span class="has-text-weight-bold is-size-4">{{ adoption_notice.num_per_sex.M_N }}</span>
|
||||
<span class="has-text-weight-bold is-size-4">{{ num_per_sex.M_N }}</span>
|
||||
<span class="icon">
|
||||
<img class="icon"
|
||||
src="{% static 'fellchensammlung/img/sexes/Male Neutered.png' %}"
|
||||
@@ -41,4 +42,5 @@
|
||||
</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
@@ -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>
|
||||
@@ -38,7 +38,7 @@
|
||||
<div class="grid is-col-min-15">
|
||||
{% for rescue_org in rescue_orgs_to_check %}
|
||||
<div class="cell">
|
||||
{% include "fellchensammlung/partials/partial-check-rescue-org.html" %}
|
||||
{% include "fellchensammlung/partials/rescue_orgs/partial-check-rescue-org.html" %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -50,7 +50,7 @@
|
||||
<div class="grid is-col-min-15">
|
||||
{% for rescue_org in rescue_orgs_with_ongoing_communication %}
|
||||
<div class="cell">
|
||||
{% include "fellchensammlung/partials/partial-check-rescue-org.html" %}
|
||||
{% include "fellchensammlung/partials/rescue_orgs/partial-check-rescue-org.html" %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -62,7 +62,7 @@
|
||||
<div class="grid is-col-min-15">
|
||||
{% for rescue_org in rescue_orgs_last_checked %}
|
||||
<div class="cell">
|
||||
{% include "fellchensammlung/partials/partial-check-rescue-org.html" %}
|
||||
{% include "fellchensammlung/partials/rescue_orgs/partial-check-rescue-org.html" %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
43
src/fellchensammlung/templates/mfa/recovery_codes/index.html
Normal file
43
src/fellchensammlung/templates/mfa/recovery_codes/index.html
Normal file
@@ -0,0 +1,43 @@
|
||||
{% extends "mfa/recovery_codes/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load allauth %}
|
||||
{% block content %}
|
||||
{% element h1 %}
|
||||
{% translate "Recovery Codes" %}
|
||||
{% endelement %}
|
||||
{% element p %}
|
||||
{% blocktranslate count unused_count=unused_codes|length %}There is {{ unused_count }} out of {{ total_count }}
|
||||
recovery codes available.{% plural %}There are {{ unused_count }} out of {{ total_count }} recovery codes
|
||||
available.{% endblocktranslate %}
|
||||
{% endelement %}
|
||||
<div class="block">
|
||||
{% element field id="recovery_codes" type="textarea" disabled=True rows=unused_codes|length readonly=True %}
|
||||
{% slot label %}
|
||||
{% translate "Unused codes" %}
|
||||
{% endslot %}
|
||||
{% comment %} djlint:off {% endcomment %}
|
||||
{% slot value %}{% for code in unused_codes %}{% if forloop.counter0 %}
|
||||
{% endif %}{{ code }}{% endfor %}{% endslot %}
|
||||
{% comment %} djlint:on {% endcomment %}
|
||||
{% endelement %}
|
||||
</div>
|
||||
<div class="block">
|
||||
<div class="field is-grouped is-grouped-multiline">
|
||||
{% if unused_codes %}
|
||||
{% url 'mfa_download_recovery_codes' as download_url %}
|
||||
<div class="control">
|
||||
{% element button href=download_url %}
|
||||
{% translate "Download codes" %}
|
||||
{% endelement %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% url 'mfa_generate_recovery_codes' as generate_url %}
|
||||
|
||||
<div class="control">
|
||||
{% element button href=generate_url %}
|
||||
{% translate "Generate new codes" %}
|
||||
{% endelement %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,78 @@
|
||||
{% extends "mfa/webauthn/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load allauth %}
|
||||
{% load humanize %}
|
||||
{% block content %}
|
||||
{% element h1 %}
|
||||
{% trans "Security Keys" %}
|
||||
{% endelement %}
|
||||
{% if authenticators|length == 0 %}
|
||||
{% element p %}
|
||||
{% blocktranslate %}No security keys have been added.{% endblocktranslate %}
|
||||
{% endelement %}
|
||||
{% else %}
|
||||
<article class="panel">
|
||||
<p class="panel-heading">{% trans "Security Keys" %}</p>
|
||||
{% for authenticator in authenticators %}
|
||||
<div class="panel-block">
|
||||
<div class="level" style="width: 100%;">
|
||||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
{% if authenticator.wrap.is_passwordless is True %}
|
||||
{% element badge tags="mfa,key,primary" %}
|
||||
{% translate "Passkey" %}
|
||||
{% endelement %}
|
||||
{% elif authenticator.wrap.is_passwordless is False %}
|
||||
{% element badge tags="mfa,key,secondary" %}
|
||||
{% translate "Security key" %}
|
||||
{% endelement %}
|
||||
{% else %}
|
||||
{% element badge title=_("This key does not indicate whether it is a passkey.") tags="mfa,key,warning" %}
|
||||
{% translate "Unspecified" %}
|
||||
{% endelement %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="level-item">
|
||||
<strong>
|
||||
{{ authenticator }}
|
||||
</strong>
|
||||
</div>
|
||||
<div class="level-item">
|
||||
{% blocktranslate with created_at=authenticator.created_at|date:"SHORT_DATE_FORMAT" %}
|
||||
Added
|
||||
on {{ created_at }}{% endblocktranslate %}.
|
||||
</div>
|
||||
<div class="level-item">
|
||||
{% if authenticator.last_used_at %}
|
||||
{% blocktranslate with last_used=authenticator.last_used_at|naturaltime %}Last used
|
||||
{{ last_used }}{% endblocktranslate %}
|
||||
{% else %}
|
||||
Not used.
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<div class="level-item">
|
||||
{% url 'mfa_edit_webauthn' pk=authenticator.pk as edit_url %}
|
||||
{% element button tags="mfa,authenticator,edit,tool" href=edit_url %}
|
||||
{% translate "Edit" %}
|
||||
{% endelement %}
|
||||
</div>
|
||||
<div class="level-item">
|
||||
{% url 'mfa_remove_webauthn' pk=authenticator.pk as remove_url %}
|
||||
{% element button tags="mfa,authenticator,danger,delete,tool" href=remove_url %}
|
||||
{% translate "Remove" %}
|
||||
{% endelement %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</article>
|
||||
{% endif %}
|
||||
{% url 'mfa_add_webauthn' as add_url %}
|
||||
{% element button href=add_url %}
|
||||
{% translate "Add" %}
|
||||
{% endelement %}
|
||||
{% endblock %}
|
||||
@@ -5,6 +5,7 @@ from django.template.defaultfilters import stringfilter
|
||||
from django.utils.safestring import mark_safe
|
||||
from urllib.parse import urlparse
|
||||
from django.utils import timezone
|
||||
from django.urls import reverse
|
||||
|
||||
from fellchensammlung.tools.misc import time_since_as_hr_string
|
||||
from notfellchen import settings
|
||||
@@ -54,6 +55,11 @@ def get_oxitraffic_script_if_enabled():
|
||||
return ""
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def api_base_url():
|
||||
return reverse("api-base-url")
|
||||
|
||||
|
||||
@register.filter
|
||||
@stringfilter
|
||||
def pointdecimal(value):
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import logging
|
||||
import requests
|
||||
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
|
||||
|
||||
|
||||
@@ -86,7 +87,10 @@ class FediClient:
|
||||
|
||||
|
||||
def post_an_to_fedi(adoption_notice):
|
||||
try:
|
||||
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}
|
||||
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,
|
||||
url=response['url'], )
|
||||
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
|
||||
|
||||
|
||||
def export_svg(adoption_notice):
|
||||
result = render_to_string(template_name="fellchensammlung/images/adoption-notice.svg",
|
||||
def export_svg(adoption_notice, template_name: str = "fellchensammlung/images/adoption-notice.svg"):
|
||||
result = render_to_string(template_name=template_name,
|
||||
context={"adoption_notice": adoption_notice, })
|
||||
return result
|
||||
|
||||
@@ -2,14 +2,19 @@ from datetime import timedelta
|
||||
|
||||
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():
|
||||
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()
|
||||
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()
|
||||
|
||||
try:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import datetime as datetime
|
||||
import logging
|
||||
import time
|
||||
|
||||
from django.utils.translation import ngettext
|
||||
from django.utils.translation import gettext as _
|
||||
@@ -75,3 +76,23 @@ def is_404(url):
|
||||
return result.status_code == 404
|
||||
except requests.RequestException as e:
|
||||
logging.warning(f"Request to {url} failed: {e}")
|
||||
|
||||
|
||||
class RequestProfiler:
|
||||
data = []
|
||||
|
||||
def clear(self):
|
||||
self.data = []
|
||||
|
||||
def add_status(self, status):
|
||||
self.data.append((time.time(), status))
|
||||
|
||||
@property
|
||||
def as_relative(self):
|
||||
first_ts = self.data[0][0]
|
||||
return [(datum[0] - first_ts, datum[1]) for datum in self.data]
|
||||
|
||||
@property
|
||||
def as_relative_with_ms(self):
|
||||
first_ts = self.data[0][0]
|
||||
return [(f"{(datum[0] - first_ts)*1000:.4}ms", datum[1]) for datum in self.data]
|
||||
|
||||
@@ -69,14 +69,14 @@ class AdoptionNoticeStatusChoices:
|
||||
UNCHECKED = "awaiting_action_unchecked", _("Unchecked")
|
||||
|
||||
class Closed(TextChoices):
|
||||
SUCCESSFUL_WITH_NOTFELLCHEN = "closed_successful_with_notfellchen", _("Successful (with Notfellchen)")
|
||||
SUCCESSFUL_WITHOUT_NOTFELLCHEN = "closed_successful_without_notfellchen", _("Successful (without Notfellchen)")
|
||||
ANIMAL_DIED = "closed_animal_died", _("Animal died")
|
||||
FOR_OTHER_ADOPTION_NOTICE = "closed_for_other_adoption_notice", _("Closed for other adoption notice")
|
||||
NOT_OPEN_ANYMORE = "closed_not_open_for_adoption_anymore", _("Not open for adoption anymore")
|
||||
SUCCESSFUL = "closed_successfully", _("Erfolgreich vermittelt")
|
||||
ANIMAL_DIED = "closed_animal_died", _("Tier gestorben")
|
||||
FOR_OTHER_ADOPTION_NOTICE = ("closed_for_other_adoption_notice",
|
||||
_("Vermittlung wurde zugunsten einer anderen geschlossen."))
|
||||
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", _(
|
||||
"Der Link zu weiteren Informationen ist nicht mehr erreichbar.")
|
||||
OTHER = "closed_other", _("Other (closed)")
|
||||
OTHER = "closed_other", _("Anderes")
|
||||
|
||||
class Disabled(TextChoices):
|
||||
AGAINST_RULES = "disabled_against_the_rules", _("Against the rules")
|
||||
@@ -97,8 +97,7 @@ class AdoptionNoticeStatusChoicesDescriptions:
|
||||
_ansc = AdoptionNoticeStatusChoices # Mapping for readability
|
||||
mapping = {_ansc.Active.SEARCHING.value: "",
|
||||
_ansc.Active.INTERESTED: _("Jemand hat bereits Interesse an den Tieren."),
|
||||
_ansc.Closed.SUCCESSFUL_WITH_NOTFELLCHEN: _("Vermittlung erfolgreich abgeschlossen."),
|
||||
_ansc.Closed.SUCCESSFUL_WITHOUT_NOTFELLCHEN: _("Vermittlung erfolgreich abgeschlossen."),
|
||||
_ansc.Closed.SUCCESSFUL: _("Vermittlung erfolgreich abgeschlossen."),
|
||||
_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.NOT_OPEN_ANYMORE: _("Tier(e) stehen nicht mehr zur Vermittlung bereit."),
|
||||
@@ -110,10 +109,11 @@ class AdoptionNoticeStatusChoicesDescriptions:
|
||||
_ansc.AwaitingAction.WAITING_FOR_REVIEW: _(
|
||||
"Deaktiviert bis Moderator*innen die Vermittlung prüfen können."),
|
||||
_ansc.AwaitingAction.NEEDS_ADDITIONAL_INFO: _("Deaktiviert bis Informationen nachgetragen werden."),
|
||||
_ansc.AwaitingAction.UNCHECKED: _("Vermittlung deaktiviert bis sie vom Team auf Aktualität geprüft wurde."),
|
||||
_ansc.AwaitingAction.UNCHECKED: _(
|
||||
"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.OTHER: _("Vermittlung deaktiviert.")
|
||||
_ansc.Disabled.AGAINST_RULES: _("Die Vermittlung wurde gesperrt, da sie gegen die Regeln verstößt."),
|
||||
_ansc.Disabled.OTHER: _("Vermittlung gesperrt.")
|
||||
}
|
||||
|
||||
|
||||
@@ -125,3 +125,22 @@ class AdoptionNoticeProcessTemplates:
|
||||
_bp = "fellchensammlung/partials/adoption_process/" # Base path for ease
|
||||
mapping = {AdoptionProcess.CONTACT_PERSON_IN_AN: f"{_bp}contact_person_in_an.html",
|
||||
}
|
||||
|
||||
|
||||
class RegularCheckStatusChoices(models.TextChoices):
|
||||
REGULAR_CHECK = "regular_check", _("Wird regelmäßig geprüft")
|
||||
EXCLUDED_NO_ONLINE_LISTING = "excluded_no_online_listing", _("Exkludiert: Tiere werden nicht online gelistet")
|
||||
EXCLUDED_OTHER_ORG = "excluded_other_org", _("Exkludiert: Andere Organisation wird geprüft")
|
||||
EXCLUDED_SCOPE = "excluded_scope", _("Exkludiert: Organisation hat nie Notfellchen-relevanten Vermittlungen")
|
||||
EXCLUDED_OTHER = "excluded_other", _("Exkludiert: Anderer Grund")
|
||||
|
||||
|
||||
##########
|
||||
## USER ##
|
||||
##########
|
||||
|
||||
reason_for_signup_label = _("Grund für die Registrierung")
|
||||
reason_for_signup_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.")
|
||||
|
||||
@@ -173,6 +173,7 @@ class AdoptionNoticeSearch:
|
||||
|
||||
class RescueOrgSearch:
|
||||
def __init__(self, request):
|
||||
self.name = None
|
||||
self.area_search = None
|
||||
self.max_distance = None
|
||||
self.location = None # Can either be Location (DjangoModel) or LocationProxy
|
||||
@@ -229,6 +230,7 @@ class RescueOrgSearch:
|
||||
return fitting_rescue_orgs
|
||||
|
||||
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":
|
||||
self.search_form = RescueOrgSearchForm(request.GET)
|
||||
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"),
|
||||
# ex: /adoption_notice/7/
|
||||
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
|
||||
path("vermittlung/<int:adoption_notice_id>/sharepic", views.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
|
||||
path("vermittlung/<int:adoption_notice_id>/edit", views.adoption_notice_edit, name="adoption-notice-edit"),
|
||||
# ex: /vermittlung/5/add-photo
|
||||
@@ -41,12 +47,19 @@ urlpatterns = [
|
||||
# ex: /adoption_notice/2/add-animal
|
||||
path("vermittlung/<int:adoption_notice_id>/add-animal", views.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"),
|
||||
|
||||
path("tierschutzorganisationen/", views.list_rescue_organizations, name="rescue-organizations"),
|
||||
path("tierschutzorganisationen/<int:rescue_organization_id>/", views.detail_view_rescue_organization,
|
||||
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,
|
||||
name="rescue-organization-exclude"),
|
||||
path("tierschutzorganisationen/add-exclusion-reason", views.update_exclusion_reason,
|
||||
name="rescue-organization-add-exclusion-reason"),
|
||||
path("tierschutzorganisationen/spezialisierung/<slug:species_slug>", views.specialized_rescues,
|
||||
name="specialized-rescue-organizations"),
|
||||
|
||||
@@ -86,20 +99,12 @@ urlpatterns = [
|
||||
###########
|
||||
# ex: user/1
|
||||
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/notifications/", views.my_notifications, name="user-notifications"),
|
||||
path('user/me/export/', views.export_own_profile, name='user-me-export'),
|
||||
|
||||
path('accounts/register/',
|
||||
registration_views.HTMLMailRegistrationView.as_view(
|
||||
form_class=CustomRegistrationForm,
|
||||
email_body_template="fellchensammlung/mail/activation_email.html",
|
||||
),
|
||||
name='django_registration_register',
|
||||
),
|
||||
path('accounts/', include('django_registration.backends.activation.urls')),
|
||||
path('accounts/', include('django.contrib.auth.urls')),
|
||||
|
||||
path('change-language', views.change_language, name="change-language"),
|
||||
|
||||
###########
|
||||
@@ -125,7 +130,7 @@ urlpatterns = [
|
||||
###################
|
||||
## External Site ##
|
||||
###################
|
||||
path('bulma/external-site/', views.external_site_warning, name="external-site"),
|
||||
path('external-site/', views.external_site_warning, name="external-site"),
|
||||
|
||||
###############
|
||||
## TECHNICAL ##
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
from django.contrib.auth.views import redirect_to_login
|
||||
from django.core.paginator import Paginator
|
||||
@@ -14,7 +13,6 @@ from django.contrib.auth.decorators import user_passes_test
|
||||
from django.core.serializers import serialize
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
import json
|
||||
import requests
|
||||
|
||||
from .mail import notify_mods_new_report
|
||||
from notfellchen import settings
|
||||
@@ -23,12 +21,14 @@ from fellchensammlung import logger
|
||||
from .models import AdoptionNotice, Text, Animal, Rule, Image, Report, ModerationAction, \
|
||||
User, Location, Subscriptions, Notification, RescueOrganization, \
|
||||
Species, Log, Timestamp, TrustLevel, SexChoicesWithAll, SearchSubscription, \
|
||||
ImportantLocation, SpeciesSpecificURL, NotificationTypeChoices, SocialMediaPost
|
||||
ImportantLocation, SpeciesSpecificURL, NotificationTypeChoices, SocialMediaPost, AllowUseOfMaterialsChices
|
||||
from .forms import AdoptionNoticeForm, ImageForm, ReportAdoptionNoticeForm, \
|
||||
CommentForm, ReportCommentForm, AnimalForm, AdoptionNoticeFormAutoAnimal, SpeciesURLForm, RescueOrgInternalComment
|
||||
CommentForm, ReportCommentForm, AnimalForm, AdoptionNoticeFormAutoAnimal, SpeciesURLForm, RescueOrgInternalComment, \
|
||||
UpdateRescueOrgRegularCheckStatus, UserModCommentForm, CloseAdoptionNoticeForm, RescueOrgSearchByNameForm, \
|
||||
RescueOrgForm
|
||||
from .models import Language, Announcement
|
||||
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.metrics import gather_metrics_data, get_rescue_org_check_stats
|
||||
from .tools.admin import clean_locations, get_unchecked_adoption_notices, deactivate_unchecked_adoption_notices, \
|
||||
@@ -36,7 +36,8 @@ from .tools.admin import clean_locations, get_unchecked_adoption_notices, deacti
|
||||
from .tasks import post_adoption_notice_save
|
||||
from rest_framework.authtoken.models import Token
|
||||
|
||||
from .tools.model_helpers import AdoptionNoticeStatusChoices, AdoptionNoticeProcessTemplates
|
||||
from .tools.misc import RequestProfiler
|
||||
from .tools.model_helpers import AdoptionNoticeStatusChoices, AdoptionNoticeProcessTemplates, RegularCheckStatusChoices
|
||||
from .tools.search import AdoptionNoticeSearch, RescueOrgSearch
|
||||
|
||||
|
||||
@@ -113,6 +114,13 @@ def handle_an_check_actions(request, action, adoption_notice=None):
|
||||
|
||||
def adoption_notice_detail(request, 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
|
||||
if request.user.is_authenticated:
|
||||
try:
|
||||
@@ -241,11 +249,17 @@ def search_important_locations(request, important_location_slug):
|
||||
|
||||
|
||||
def search(request, templatename="fellchensammlung/search.html"):
|
||||
search_profile = RequestProfiler()
|
||||
search_profile.add_status("Start")
|
||||
|
||||
# A user just visiting the search site did not search, only upon completing the search form a user has really
|
||||
# searched. This will toggle the "subscribe" button
|
||||
searched = False
|
||||
search_profile.add_status("Init Search")
|
||||
search = AdoptionNoticeSearch()
|
||||
search_profile.add_status("Search from request starting")
|
||||
search.adoption_notice_search_from_request(request)
|
||||
search_profile.add_status("Search from request finished")
|
||||
if request.method == 'POST':
|
||||
searched = True
|
||||
if "subscribe_to_search" in request.POST:
|
||||
@@ -265,10 +279,12 @@ def search(request, templatename="fellchensammlung/search.html"):
|
||||
subscribed_search = search.get_subscription_or_none(request.user)
|
||||
else:
|
||||
subscribed_search = None
|
||||
search_profile.add_status("End of POST")
|
||||
site_title = _("Suche")
|
||||
site_description = _("Ratten in Tierheimen und Rattenhilfen in der Nähe suchen.")
|
||||
canonical_url = reverse("search")
|
||||
|
||||
search_profile.add_status("Start of context")
|
||||
context = {"adoption_notices": search.get_adoption_notices(),
|
||||
"search_form": search.search_form,
|
||||
"place_not_found": search.place_not_found,
|
||||
@@ -285,6 +301,10 @@ def search(request, templatename="fellchensammlung/search.html"):
|
||||
"site_title": site_title,
|
||||
"site_description": site_description,
|
||||
"canonical_url": canonical_url}
|
||||
search_profile.add_status("End of context")
|
||||
if request.user.is_superuser:
|
||||
context["profile"] = search_profile.as_relative_with_ms
|
||||
search_profile.add_status("Finished - returing render")
|
||||
return render(request, templatename, context=context)
|
||||
|
||||
|
||||
@@ -577,12 +597,23 @@ def report_detail_success(request, report_id):
|
||||
|
||||
|
||||
def user_detail(request, user, token=None):
|
||||
user_detail_profile = RequestProfiler()
|
||||
user_detail_profile.add_status("Start")
|
||||
adoption_notices = AdoptionNotice.objects.filter(owner=user)
|
||||
user_detail_profile.add_status("Finished fetching adoption notices")
|
||||
context = {"user": user,
|
||||
"adoption_notices": AdoptionNotice.objects.filter(owner=user),
|
||||
"adoption_notices": adoption_notices,
|
||||
"notifications": Notification.objects.filter(user_to_notify=user, read=False),
|
||||
"search_subscriptions": SearchSubscription.objects.filter(owner=user), }
|
||||
user_detail_profile.add_status("End of context")
|
||||
if token is not None:
|
||||
context["token"] = token
|
||||
user_detail_profile.add_status("Finished - returning to renderer")
|
||||
if request.user.is_superuser:
|
||||
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)
|
||||
|
||||
|
||||
@@ -653,10 +684,40 @@ def my_notifications(request):
|
||||
context = {"notifications_unread": Notification.objects.filter(user_to_notify=request.user, read=False).order_by(
|
||||
"-created_at"),
|
||||
"notifications_read_last": Notification.objects.filter(user_to_notify=request.user,
|
||||
read=True).order_by("-read_at")}
|
||||
read=True).order_by("-read_at")[:10]}
|
||||
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)
|
||||
def modqueue(request):
|
||||
open_reports = Report.objects.select_related("reportadoptionnotice", "reportcomment").filter(status=Report.WAITING)
|
||||
@@ -790,6 +851,7 @@ def list_rescue_organizations(request, species=None, template='fellchensammlung/
|
||||
context = {"rescue_organizations_to_list": rescue_organizations_to_list,
|
||||
"show_rescue_orgs": True,
|
||||
"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:
|
||||
additional_context = {
|
||||
@@ -845,8 +907,10 @@ def rescue_organization_check(request, context=None):
|
||||
action = request.POST.get("action")
|
||||
if action == "checked":
|
||||
rescue_org.set_checked()
|
||||
elif action == "exclude":
|
||||
rescue_org.set_exclusion_from_checks()
|
||||
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":
|
||||
species_url_form = SpeciesURLForm(request.POST)
|
||||
|
||||
@@ -857,10 +921,13 @@ def rescue_organization_check(request, context=None):
|
||||
elif action == "update_internal_comment":
|
||||
comment_form = RescueOrgInternalComment(request.POST, instance=rescue_org)
|
||||
if comment_form.is_valid():
|
||||
Log.objects.create(user=request.user, action="rescue_organization_added_internal_comment", )
|
||||
comment_form.save()
|
||||
|
||||
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(
|
||||
"updated_at")
|
||||
rescue_orgs_last_checked = RescueOrganization.objects.filter().order_by("-last_checked")[:10]
|
||||
@@ -896,50 +963,116 @@ def rescue_organization_check_dq(request):
|
||||
return rescue_organization_check(request, context)
|
||||
|
||||
|
||||
def exclude_from_regular_check(request, rescue_organization_id, source="organization-check"):
|
||||
rescue_org = get_object_or_404(RescueOrganization, pk=rescue_organization_id)
|
||||
if request.method == "POST":
|
||||
form = UpdateRescueOrgRegularCheckStatus(request.POST, instance=rescue_org)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
to_be_excluded = form.cleaned_data["regular_check_status"] != RegularCheckStatusChoices.REGULAR_CHECK
|
||||
rescue_org.exclude_from_check = to_be_excluded
|
||||
rescue_org.save()
|
||||
if to_be_excluded:
|
||||
Log.objects.create(user=request.user,
|
||||
action="rescue_organization_excluded_from_check",
|
||||
text=f"New status: {form.cleaned_data['regular_check_status']}")
|
||||
|
||||
return redirect(reverse(source))
|
||||
else:
|
||||
form = UpdateRescueOrgRegularCheckStatus(instance=rescue_org)
|
||||
org_meta = rescue_org._meta
|
||||
context = {"form": form, "org": rescue_org, "org_meta": org_meta}
|
||||
return render(request, 'fellchensammlung/forms/form-exclude-org-from-check.html', context=context)
|
||||
|
||||
|
||||
@user_passes_test(user_is_trust_level_or_above)
|
||||
def update_exclusion_reason(request):
|
||||
"""
|
||||
This view will redirect to update a rescue org that not yet has an exclusion reason but is excluded
|
||||
"""
|
||||
orgs_to_check = RescueOrganization.objects.filter(exclude_from_check=True,
|
||||
regular_check_status=RegularCheckStatusChoices.REGULAR_CHECK)
|
||||
if orgs_to_check.count() > 0:
|
||||
return exclude_from_regular_check(request, orgs_to_check[0].pk,
|
||||
source="rescue-organization-add-exclusion-reason")
|
||||
else:
|
||||
return render(request, "fellchensammlung/errors/404.html", status=404)
|
||||
|
||||
|
||||
@user_passes_test(user_is_trust_level_or_above)
|
||||
def moderation_tools_overview(request):
|
||||
context = None
|
||||
if request.method == "POST":
|
||||
action = request.POST.get("action")
|
||||
if action == "post_to_fedi":
|
||||
adoption_notice = 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.")}
|
||||
else:
|
||||
context = {"action_was_posting": True,
|
||||
"posted_successfully": False,
|
||||
"error_message": _("Keine Vermittlung zum Posten gefunden.")}
|
||||
context = handle_post_fedi_action()
|
||||
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)
|
||||
if request.method == "POST":
|
||||
reason_for_closing = request.POST.get("reason_for_closing")
|
||||
if reason_for_closing not in AdoptionNoticeStatusChoices.Closed.values:
|
||||
return render(request, "fellchensammlung/errors/403.html", status=403)
|
||||
adoption_notice.adoption_notice_status = reason_for_closing
|
||||
adoption_notice.save()
|
||||
form = CloseAdoptionNoticeForm(request.POST, instance=adoption_notice)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
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)
|
||||
|
||||
|
||||
def adoption_notice_sharepic(request, adoption_notice_id):
|
||||
adoption_notice = get_object_or_404(AdoptionNotice, pk=adoption_notice_id)
|
||||
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
@@ -9,7 +9,7 @@ https://docs.djangoproject.com/en/3.2/topics/settings/
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/3.2/ref/settings/
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
import os
|
||||
import configparser
|
||||
@@ -74,6 +74,12 @@ except configparser.NoSectionError:
|
||||
raise BaseException("No config found or no Django Secret is configured!")
|
||||
DEBUG = config.getboolean('django', 'debug', fallback=False)
|
||||
|
||||
# Internal IPs
|
||||
internal_ip_raw_config_value = config.get("django", "internal_ips", fallback=None)
|
||||
if internal_ip_raw_config_value:
|
||||
INTERNAL_IPS = json.loads(internal_ip_raw_config_value)
|
||||
|
||||
|
||||
""" DATABASE """
|
||||
DB_BACKEND = config.get("database", "backend", fallback="sqlite3")
|
||||
DB_NAME = config.get("database", "name", fallback="notfellchen.sqlite3")
|
||||
@@ -130,6 +136,37 @@ ACCOUNT_ACTIVATION_DAYS = 7 # One-week activation window
|
||||
REGISTRATION_OPEN = True
|
||||
REGISTRATION_SALT = "notfellchen"
|
||||
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
# Needed to login by username in Django admin, regardless of `allauth`
|
||||
'django.contrib.auth.backends.ModelBackend',
|
||||
|
||||
# `allauth` specific authentication methods, such as login by email
|
||||
'allauth.account.auth_backends.AuthenticationBackend',
|
||||
]
|
||||
|
||||
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
|
||||
ACCOUNT_EMAIL_VERIFICATION_BY_CODE_ENABLED = True
|
||||
|
||||
ACCOUNT_SIGNUP_FIELDS = ['username*', "email*", "password1*"]
|
||||
|
||||
ACCOUNT_SIGNUP_FORM_CLASS = 'fellchensammlung.forms.AddedRegistrationForm'
|
||||
|
||||
MFA_SUPPORTED_TYPES = ["totp",
|
||||
"webauthn",
|
||||
"recovery_codes"]
|
||||
|
||||
MFA_TOTP_TOLERANCE = 1
|
||||
MFA_TOTP_ISSUER = config.get('security', 'totp_issuer', fallback="Notfellchen")
|
||||
|
||||
MFA_PASSKEY_LOGIN_ENABLED = True
|
||||
MFA_PASSKEY_SIGNUP_ENABLED = True
|
||||
|
||||
# Optional -- use for local development only: the WebAuthn uses the
|
||||
#``fido2`` package, and versions up to including version 1.1.3 do not
|
||||
# regard localhost as a secure origin, which is problematic during
|
||||
# local development and testing.
|
||||
MFA_WEBAUTHN_ALLOW_INSECURE_ORIGIN = config.get('security', 'webauth_allow_insecure_origin', fallback=False)
|
||||
|
||||
""" SECURITY.TXT """
|
||||
SEC_CONTACT = config.get("security", "Contact", fallback="julian-samuel@gebuehr.net")
|
||||
SEC_EXPIRES = config.get("security", "Expires", fallback="2028-03-17T07:00:00.000Z")
|
||||
@@ -182,7 +219,11 @@ INSTALLED_APPS = [
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
"django.contrib.humanize",
|
||||
'django.contrib.messages',
|
||||
'allauth',
|
||||
'allauth.account',
|
||||
'allauth.mfa',
|
||||
'django.contrib.staticfiles',
|
||||
"django.contrib.sitemaps",
|
||||
'fontawesomefree',
|
||||
@@ -193,11 +234,15 @@ INSTALLED_APPS = [
|
||||
'rest_framework.authtoken',
|
||||
'drf_spectacular',
|
||||
'drf_spectacular_sidecar', # required for Django collectstatic discovery
|
||||
'widget_tweaks'
|
||||
'widget_tweaks',
|
||||
"debug_toolbar",
|
||||
'admin_extra_buttons',
|
||||
'simple_history',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
"debug_toolbar.middleware.DebugToolbarMiddleware",
|
||||
# Static file serving & caching
|
||||
'whitenoise.middleware.WhiteNoiseMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
@@ -208,6 +253,9 @@ MIDDLEWARE = [
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
# allauth middleware, needs to be after message middleware
|
||||
"allauth.account.middleware.AccountMiddleware",
|
||||
'simple_history.middleware.HistoryRequestMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'notfellchen.urls'
|
||||
@@ -222,6 +270,7 @@ TEMPLATES = [
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
# Needed for allauth
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.template.context_processors.media',
|
||||
|
||||
@@ -18,12 +18,14 @@ from django.conf.urls.i18n import i18n_patterns
|
||||
from django.contrib import admin
|
||||
from django.urls import include, path
|
||||
from django.conf import settings
|
||||
|
||||
from django.conf.urls.static import static
|
||||
|
||||
from debug_toolbar.toolbar import debug_toolbar_urls
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
]
|
||||
path('accounts/', include('allauth.urls')),
|
||||
] + debug_toolbar_urls()
|
||||
|
||||
urlpatterns += i18n_patterns(
|
||||
path("", include("fellchensammlung.urls")),
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<div class="block">
|
||||
<a class="button is-warning" href="{% url 'password_reset' %}">{% translate "Passwort vergessen?" %}</a>
|
||||
<a class="button is-link"
|
||||
href="{% url 'django_registration_register' %}">{% translate "Registrieren" %}</a>
|
||||
href="{% url 'account_signup' %}">{% translate "Registrieren" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
Reference in New Issue
Block a user