Compare commits
70 Commits
964aeb97a7
...
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 | |||
| e81618500b | |||
| f7a5da306c | |||
| 92a9b5c6c9 |
@@ -9,15 +9,14 @@ RUN apt install gettext -y
|
|||||||
RUN apt install libpq-dev gcc -y
|
RUN apt install libpq-dev gcc -y
|
||||||
COPY . /app
|
COPY . /app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN mkdir /app/data
|
RUN mkdir /app/data/static -p
|
||||||
RUN mkdir /app/data/static
|
|
||||||
RUN mkdir /app/data/media
|
RUN mkdir /app/data/media
|
||||||
RUN pip install -e . # Without the -e the library static folder will not be copied by collectstatic!
|
RUN pip install --no-cache-dir -e . # Without the -e the library static folder will not be copied by collectstatic!
|
||||||
|
|
||||||
RUN nf collectstatic --noinput
|
RUN nf collectstatic --noinput
|
||||||
RUN nf compilemessages --ignore venv
|
RUN nf compilemessages --ignore venv
|
||||||
|
|
||||||
COPY docker/notfellchen.bash /bin/notfellchen
|
COPY docker/entrypoint.sh /bin/notfellchen
|
||||||
|
|
||||||
EXPOSE 7345
|
EXPOSE 7345
|
||||||
CMD ["notfellchen"]
|
CMD ["notfellchen"]
|
||||||
|
|||||||
@@ -15,32 +15,44 @@ class DrawioDirective(SphinxDirective):
|
|||||||
.. drawio::
|
.. drawio::
|
||||||
example-diagram.drawio.html
|
example-diagram.drawio.html
|
||||||
example-diagram.drawio.png
|
example-diagram.drawio.png
|
||||||
|
:alt: Example of a Draw.io diagram
|
||||||
"""
|
"""
|
||||||
|
|
||||||
has_content = False
|
has_content = False
|
||||||
required_arguments = 2 # html and png
|
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]:
|
def run(self) -> list[nodes.Node]:
|
||||||
html_path = self.arguments[0]
|
|
||||||
png_path = self.arguments[1]
|
|
||||||
|
|
||||||
env = self.state.document.settings.env
|
env = self.state.document.settings.env
|
||||||
|
builder = env.app.builder
|
||||||
|
|
||||||
|
# Resolve paths relative to the document
|
||||||
docdir = Path(env.doc2path(env.docname)).parent
|
docdir = Path(env.doc2path(env.docname)).parent
|
||||||
html_rel = Path(self.arguments[0])
|
html_rel = Path(self.arguments[0])
|
||||||
png_rel = Path(self.arguments[1])
|
png_rel = Path(self.arguments[1])
|
||||||
|
|
||||||
html_path = (docdir / html_rel).resolve()
|
html_path = (docdir / html_rel).resolve()
|
||||||
png_path = (docdir / png_rel).resolve()
|
png_path = (docdir / png_rel).resolve()
|
||||||
|
|
||||||
|
alt_text = self.options.get("alt", "")
|
||||||
|
|
||||||
container = nodes.container()
|
container = nodes.container()
|
||||||
|
|
||||||
# HTML output -> raw HTML node
|
# HTML output -> raw HTML node
|
||||||
if self.builder.format == "html":
|
if builder.format == "html":
|
||||||
# Embed the HTML file contents directly
|
# 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(
|
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",
|
format="html",
|
||||||
)
|
)
|
||||||
container += raw_html_node
|
container += raw_html_node
|
||||||
@@ -51,17 +63,12 @@ class DrawioDirective(SphinxDirective):
|
|||||||
|
|
||||||
return [container]
|
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:
|
def setup(app: Sphinx) -> ExtensionMetadata:
|
||||||
app.add_directive("drawio", DrawioDirective)
|
app.add_directive("drawio", DrawioDirective)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"version": "0.1",
|
"version": "0.2",
|
||||||
"parallel_read_safe": True,
|
"parallel_read_safe": True,
|
||||||
"parallel_write_safe": True,
|
"parallel_write_safe": True,
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
docs/user/Vermittlung-Lifecycle.drawio.png
Normal file
|
After Width: | Height: | Size: 150 KiB |
11
docs/user/Vermittlung_Lifecycle.drawio.html
Normal file
@@ -6,14 +6,27 @@ Jede Vermittlung kann abonniert werden. Dafür klickst du auf die Glocke neben d
|
|||||||
|
|
||||||
.. image:: abonnieren.png
|
.. image:: abonnieren.png
|
||||||
|
|
||||||
|
|
||||||
|
Einstellungen
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Du kannst E-Mail Benachrichtigungen in den Einstellungen deaktivieren.
|
||||||
|
|
||||||
|
.. image::
|
||||||
|
einstellungen-benachrichtigungen.png
|
||||||
|
:alt: Screenshot der Profileinstellungen in Notfellchen. Ein roter Pfeil zeigt auf einen Schalter "E-Mail Benachrichtigungen"
|
||||||
|
|
||||||
Auf der Website
|
Auf der Website
|
||||||
+++++++++++++++
|
+++++++++++++++
|
||||||
|
|
||||||
|
.. image::
|
||||||
|
screenshot-benachrichtigungen.png
|
||||||
|
:alt: Screenshot der Menüleiste von Notfellchen.org. Neben dem Symbol einer Glocke steht die Zahl 27.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
E-Mail
|
E-Mail
|
||||||
++++++
|
++++++
|
||||||
|
|
||||||
Mit während deiner :doc:`registrierung` gibst du eine E-Mail Addresse an.
|
Mit während deiner :doc:`registrierung` gibst du eine E-Mail Adresse an. An diese senden wir Benachrichtigungen, außer
|
||||||
|
du deaktiviert dies wie oben beschrieben.
|
||||||
Benachrichtigungen senden wir per Mail - du kannst das jederzeit in den Einstellungen deaktivieren.
|
|
||||||
BIN
docs/user/einstellungen-benachrichtigungen.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
58
docs/user/erste-schritte.rst
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
Erste Schritte
|
||||||
|
==============
|
||||||
|
|
||||||
|
Tiere zum Adoptieren suchen
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
Wenn du Tiere zum adoptieren suchst, brauchst du keinen Account. Du kannst bequem die `Suche <https://notfellchen.org/suchen/>`_ nutzen, um Tiere zur Adoption in deiner Nähe zu finden.
|
||||||
|
Wenn dich eine Vermittlung interessiert, kannst du folgendes tun
|
||||||
|
|
||||||
|
* die Vermittlung aufrufen um Details zu sehen
|
||||||
|
* den Link :guilabel:`Weitere Informationen` anklicken um auf der Tierheimwebsite mehr zu erfahren
|
||||||
|
* per Kommentar weitere Informationen erfragen oder hinzufügen
|
||||||
|
|
||||||
|
Wenn du die Tiere tatsächlich informieren willst, folge der Anleitung unter :guilabel:`Adoptionsprozess`.
|
||||||
|
Dieser kann sich je nach Tierschutzorganisation unterscheiden.
|
||||||
|
|
||||||
|
.. image::
|
||||||
|
screenshot-adoptionsprozess.png
|
||||||
|
:alt: Screenshot der Sektion "Adoptionsprozess" einer Vermittlungsanzeige. Der Prozess ist folgendermaßen: 1. Link zu "Weiteren Informationen" prüfen, 2. Organization kontaktieren, 3. Bei erfolgreicher Vermittlung: Vermittlung als geschlossen melden
|
||||||
|
|
||||||
|
Suchen abonnieren
|
||||||
|
+++++++++++++++++
|
||||||
|
|
||||||
|
Es kann sein, dass es in deiner Umgebung keine passenden Tiere für deine Suche gibt. Damit du nicht ständig wieder Suchen musst, gibt es die Funktion "Suche abonnieren".
|
||||||
|
Wenn du eine Suche abonnierst, wirst du für neue Vermittlungen, die den Kriterien der Suche entsprechen, benachrichtigt.
|
||||||
|
|
||||||
|
.. image::
|
||||||
|
screenshot-suche-abonnieren.png
|
||||||
|
:alt: Screenshot der Suchmaske auf Notfellchen.org . Ein roter Pfeil zeigt auf den Button "Suche abonnieren"
|
||||||
|
|
||||||
|
.. important::
|
||||||
|
|
||||||
|
Um Suchen zu abonnieren brauchst du einen Account. Wie du einen Account erstellst erfährst du hier: :doc:`registrierung`.
|
||||||
|
|
||||||
|
.. hint::
|
||||||
|
|
||||||
|
Mehr über Benachrichtigungen findest du hier: :doc:`benachrichtigungen`.
|
||||||
|
|
||||||
|
Vermittlungen hinzufügen
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
Gehe zu `Vermittlung hinzufügen <https://notfellchen.org/vermitteln/>`_ um eine neue Vermittlung einzustellen.
|
||||||
|
Füge alle Informationen die du hast hinzu.
|
||||||
|
|
||||||
|
.. important::
|
||||||
|
|
||||||
|
Um Vermittlungen hinzuzufügen brauchst du einen Account.
|
||||||
|
Wie du einen Account erstellst erfährst du hier: :doc:`registrierung`.
|
||||||
|
|
||||||
|
|
||||||
|
.. important::
|
||||||
|
|
||||||
|
Vermittlungen die du einstellst müssen erst durch Moderator\*innen freigeschaltet werden. Das passiert normalerweise
|
||||||
|
innerhalb von 24 Stunden. Wenn deine Vermittlung dann noch nicht freigeschaltet ist, prüfe bitte dein E-Mail Postfach,
|
||||||
|
es könnte sein, dass die Moderator\*innen Rückfragen haben. Melde dich gerne unter info@notfellchen.org, wenn deine
|
||||||
|
Vermittlung nach 24 Stunden nicht freigeschaltet ist.
|
||||||
|
|
||||||
|
|
||||||
@@ -1,10 +1,15 @@
|
|||||||
******************
|
****************
|
||||||
User Dokumentation
|
Benutzerhandbuch
|
||||||
******************
|
****************
|
||||||
|
|
||||||
|
Im Benutzerhandbuch findest du Informationen zur Benutzung von `notfellchen.org <https://notfellchen.org>`_.
|
||||||
|
Solltest du darüber hinaus Fragen haben, komm gerne auf uns zu: info@notfellchen.org
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
:caption: Inhalt:
|
:caption: Inhalt:
|
||||||
|
|
||||||
|
erste-schritte.rst
|
||||||
registrierung.rst
|
registrierung.rst
|
||||||
vermittlungen.rst
|
vermittlungen.rst
|
||||||
moderationskonzept.rst
|
moderationskonzept.rst
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
Tiere in Vermittlung systematisch entdecken & eintragen
|
Tiere in Vermittlung systematisch entdecken & eintragen
|
||||||
=======================================================
|
=======================================================
|
||||||
|
|
||||||
Notfellchen hat eine Liste der meisten deutschen Tierheime und anderer Tierschutzorganisationen (550, Stand Oktober 2025).
|
Notfellchen hat eine Liste der meisten deutschen Tierheime und anderer Tierschutzorganisationen.
|
||||||
Die meisten dieser Organisationen (412, Stand Oktober 2025) nehmen Tiere auf die bei Notfellchen eingetragen werden können.
|
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.
|
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::
|
.. warning::
|
||||||
|
|
||||||
Organisationen auf neue Tiere zu prüfen ist eine Funktion für Moderator\*innen. Falls du Lust hast mitzuhelfen,
|
Organisationen auf neue Tiere zu prüfen ist eine Funktion für Moderator\*innen. Falls du Lust hast mitzuhelfen,
|
||||||
|
|||||||
BIN
docs/user/screenshot-adoptionsprozess.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
docs/user/screenshot-benachrichtigungen.png
Normal file
|
After Width: | Height: | Size: 205 KiB |
BIN
docs/user/screenshot-suche-abonnieren.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
docs/user/screenshot-suche.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
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.
|
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
|
Adoption Notice Status Choices
|
||||||
++++++++++++++++++++++++++++++
|
++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ host=localhost
|
|||||||
[django]
|
[django]
|
||||||
secret=CHANGE-ME
|
secret=CHANGE-ME
|
||||||
debug=True
|
debug=True
|
||||||
|
internal_ips=["127.0.0.1"]
|
||||||
|
cache=False
|
||||||
|
|
||||||
[database]
|
[database]
|
||||||
backend=sqlite3
|
backend=sqlite3
|
||||||
@@ -28,3 +30,6 @@ django_log_level=INFO
|
|||||||
api_url=https://photon.hyteck.de/api
|
api_url=https://photon.hyteck.de/api
|
||||||
api_format=photon
|
api_format=photon
|
||||||
|
|
||||||
|
[security]
|
||||||
|
totp_issuer="NF Localhost"
|
||||||
|
webauth_allow_insecure_origin=True
|
||||||
|
|||||||
@@ -38,7 +38,11 @@ dependencies = [
|
|||||||
"celery[redis]",
|
"celery[redis]",
|
||||||
"drf-spectacular[sidecar]",
|
"drf-spectacular[sidecar]",
|
||||||
"django-widget-tweaks",
|
"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"]
|
dynamic = ["version", "readme"]
|
||||||
@@ -54,6 +58,10 @@ docs = [
|
|||||||
"sphinx-rtd-theme",
|
"sphinx-rtd-theme",
|
||||||
"sphinx-autobuild"
|
"sphinx-autobuild"
|
||||||
]
|
]
|
||||||
|
shelter-upload = [
|
||||||
|
"osm2geojson",
|
||||||
|
"tqdm"
|
||||||
|
]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
homepage = "https://notfellchen.org"
|
homepage = "https://notfellchen.org"
|
||||||
|
|||||||
66
scripts/extract_rescue_org_websites.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import argparse
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
OUTPUT_FILE = "export.csv"
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
"""Parse command-line arguments."""
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Download animal shelter data from the Overpass API to the Notfellchen API.")
|
||||||
|
parser.add_argument("--api-token", type=str, help="API token for authentication.")
|
||||||
|
parser.add_argument("--instance", type=str, help="API instance URL.")
|
||||||
|
parser.add_argument("--output-file", type=str, help="Path output file.")
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def get_config():
|
||||||
|
"""Get configuration from environment variables or command-line arguments."""
|
||||||
|
args = parse_args()
|
||||||
|
|
||||||
|
api_token = args.api_token or os.getenv("NOTFELLCHEN_API_TOKEN")
|
||||||
|
instance = args.instance or os.getenv("NOTFELLCHEN_INSTANCE")
|
||||||
|
output_file = args.output_file or OUTPUT_FILE
|
||||||
|
|
||||||
|
return api_token, instance, output_file
|
||||||
|
|
||||||
|
|
||||||
|
def rat_specific_url_or_none(org):
|
||||||
|
try:
|
||||||
|
urls = org["species_specific_urls"]
|
||||||
|
for url in urls:
|
||||||
|
# 1 is the key for rats
|
||||||
|
if url["species"] == 1:
|
||||||
|
return url["url"]
|
||||||
|
# Return none if no url for this species is found
|
||||||
|
return None
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
api_token, instance, output_file = get_config()
|
||||||
|
|
||||||
|
# Set headers and endpoint
|
||||||
|
endpoint = f"{instance}/api/organizations/"
|
||||||
|
h = {'Authorization': f'Token {api_token}', "content-type": "application/json"}
|
||||||
|
|
||||||
|
rescue_orgs_result = requests.get(endpoint, headers=h)
|
||||||
|
|
||||||
|
with open(output_file, 'w') as csvfile:
|
||||||
|
fieldnames = ['id', 'name', 'website', 'rat_specific_website']
|
||||||
|
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
||||||
|
|
||||||
|
writer.writeheader()
|
||||||
|
for org in rescue_orgs_result.json():
|
||||||
|
writer.writerow({'id': org["id"],
|
||||||
|
'name': org["name"],
|
||||||
|
'website': org["website"],
|
||||||
|
"rat_specific_website": rat_specific_url_or_none(org)})
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -133,6 +133,19 @@ def get_center_coordinates(geometry):
|
|||||||
raise ValueError(f"Unsupported geometry type: {geom_type}")
|
raise ValueError(f"Unsupported geometry type: {geom_type}")
|
||||||
|
|
||||||
|
|
||||||
|
def is_not_for_adoption(tierheim):
|
||||||
|
"""
|
||||||
|
Returns true if the shelter is not for the purpose of adoption
|
||||||
|
"""
|
||||||
|
if tierheim.purpose_adoption == "no":
|
||||||
|
return True
|
||||||
|
elif (tierheim.purpose_adoption is None and
|
||||||
|
(tierheim.purpose_sanctuary == "yes" or tierheim.purpose_release == "yes")):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
# TODO: take note of new get_overpass_result function which does the bulk of the new overpass query work
|
# TODO: take note of new get_overpass_result function which does the bulk of the new overpass query work
|
||||||
def get_overpass_result(area, data_file):
|
def get_overpass_result(area, data_file):
|
||||||
"""Build the Overpass query for fetching animal shelters in the specified area."""
|
"""Build the Overpass query for fetching animal shelters in the specified area."""
|
||||||
@@ -225,7 +238,13 @@ def main():
|
|||||||
description=get_or_none(tierheim, "opening_hours"),
|
description=get_or_none(tierheim, "opening_hours"),
|
||||||
external_object_identifier=tierheim["id"],
|
external_object_identifier=tierheim["id"],
|
||||||
EXTERNAL_SOURCE_IDENTIFIER="OSM",
|
EXTERNAL_SOURCE_IDENTIFIER="OSM",
|
||||||
|
purpose_adoption=get_or_none(tierheim, "animal_shelter:adoption"),
|
||||||
|
purpose_sanctuary=get_or_none(tierheim, "animal_shelter:sanctuary"),
|
||||||
|
purpose_release=get_or_none(tierheim, "animal_shelter:release"),
|
||||||
)
|
)
|
||||||
|
# Skip the shelter if it is not for adopting animals
|
||||||
|
if is_not_for_adoption(th_data):
|
||||||
|
continue
|
||||||
|
|
||||||
# Define here for later
|
# Define here for later
|
||||||
optional_data = ["email", "phone_number", "website", "description", "fediverse_profile", "facebook",
|
optional_data = ["email", "phone_number", "website", "description", "fediverse_profile", "facebook",
|
||||||
@@ -250,7 +269,8 @@ def main():
|
|||||||
|
|
||||||
result = requests.patch(endpoint, json=org_patch_data, headers=h)
|
result = requests.patch(endpoint, json=org_patch_data, headers=h)
|
||||||
if result.status_code != 200:
|
if result.status_code != 200:
|
||||||
logging.warning(f"Updating {tierheim['properties']['name']} failed:{result.status_code} {result.json()}")
|
logging.warning(
|
||||||
|
f"Updating {tierheim['properties']['name']} failed:{result.status_code} {result.json()}")
|
||||||
continue
|
continue
|
||||||
# Rescue organization does not exist
|
# Rescue organization does not exist
|
||||||
else:
|
else:
|
||||||
@@ -268,7 +288,8 @@ def main():
|
|||||||
|
|
||||||
if result.status_code != 201:
|
if result.status_code != 201:
|
||||||
print(f"{idx} {tierheim["properties"]["name"]}:{result.status_code} {result.json()}")
|
print(f"{idx} {tierheim["properties"]["name"]}:{result.status_code} {result.json()}")
|
||||||
print(f"Upload finished. Inserted {stats['num_inserted_orgs']} new orgs and updated {stats['num_updated_orgs']} orgs.")
|
print(
|
||||||
|
f"Upload finished. Inserted {stats['num_inserted_orgs']} new orgs and updated {stats['num_updated_orgs']} orgs.")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ from django.utils.html import format_html
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.http import urlencode
|
from django.utils.http import urlencode
|
||||||
|
|
||||||
|
from admin_extra_buttons.api import ExtraButtonsMixin, button, link
|
||||||
|
from simple_history.admin import SimpleHistoryAdmin
|
||||||
|
|
||||||
from .models import Language, Text, ReportComment, ReportAdoptionNotice, Log, Timestamp, SearchSubscription, \
|
from .models import Language, Text, ReportComment, ReportAdoptionNotice, Log, Timestamp, SearchSubscription, \
|
||||||
SpeciesSpecificURL, ImportantLocation, SocialMediaPost
|
SpeciesSpecificURL, ImportantLocation, SocialMediaPost
|
||||||
|
|
||||||
@@ -17,12 +20,34 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from .tools.model_helpers import AdoptionNoticeStatusChoices
|
from .tools.model_helpers import AdoptionNoticeStatusChoices
|
||||||
|
|
||||||
|
|
||||||
|
def export_to_csv_generic(model, queryset):
|
||||||
|
meta = model._meta
|
||||||
|
field_names = [field.name for field in meta.fields]
|
||||||
|
|
||||||
|
response = HttpResponse(content_type='text/csv')
|
||||||
|
response['Content-Disposition'] = 'attachment; filename={}.csv'.format(meta)
|
||||||
|
writer = csv.writer(response)
|
||||||
|
|
||||||
|
writer.writerow(field_names)
|
||||||
|
for obj in queryset:
|
||||||
|
row = writer.writerow([getattr(obj, field) for field in field_names])
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@admin.register(AdoptionNotice)
|
@admin.register(AdoptionNotice)
|
||||||
class AdoptionNoticeAdmin(admin.ModelAdmin):
|
class AdoptionNoticeAdmin(admin.ModelAdmin):
|
||||||
search_fields = ("name__icontains", "description__icontains")
|
search_fields = ("name__icontains", "description__icontains", "location__icontains")
|
||||||
list_filter = ("owner",)
|
list_display = ["name", "adoption_notice_status", "owner", "organization", "last_checked_hr"]
|
||||||
|
list_filter = ("adoption_notice_status", "owner")
|
||||||
actions = ("activate",)
|
actions = ("activate",)
|
||||||
|
|
||||||
|
# This admin display is only needed to get a translated label of this property
|
||||||
|
# If not present the column would show up as "last checked hr"
|
||||||
|
@admin.display(description=_("zuletzt überprüft"))
|
||||||
|
def last_checked_hr(self, obj):
|
||||||
|
return obj.last_checked_hr
|
||||||
|
|
||||||
def activate(self, request, queryset):
|
def activate(self, request, queryset):
|
||||||
for obj in queryset:
|
for obj in queryset:
|
||||||
obj.adoption_notice_status = AdoptionNoticeStatusChoices.Active.SEARCHING
|
obj.adoption_notice_status = AdoptionNoticeStatusChoices.Active.SEARCHING
|
||||||
@@ -33,7 +58,7 @@ class AdoptionNoticeAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
# Re-register UserAdmin
|
# Re-register UserAdmin
|
||||||
@admin.register(User)
|
@admin.register(User)
|
||||||
class UserAdmin(admin.ModelAdmin):
|
class UserAdmin(SimpleHistoryAdmin):
|
||||||
search_fields = ("usernamname__icontains", "first_name__icontains", "last_name__icontains", "email__icontains")
|
search_fields = ("usernamname__icontains", "first_name__icontains", "last_name__icontains", "email__icontains")
|
||||||
list_display = ("username", "email", "trust_level", "is_active", "view_adoption_notices")
|
list_display = ("username", "email", "trust_level", "is_active", "view_adoption_notices")
|
||||||
list_filter = ("is_active", "trust_level",)
|
list_filter = ("is_active", "trust_level",)
|
||||||
@@ -49,17 +74,7 @@ class UserAdmin(admin.ModelAdmin):
|
|||||||
return format_html('<a href="{}">{} Adoption Notices</a>', url, count)
|
return format_html('<a href="{}">{} Adoption Notices</a>', url, count)
|
||||||
|
|
||||||
def export_as_csv(self, request, queryset):
|
def export_as_csv(self, request, queryset):
|
||||||
meta = self.model._meta
|
response = export_to_csv_generic(self.model, queryset)
|
||||||
field_names = [field.name for field in meta.fields]
|
|
||||||
|
|
||||||
response = HttpResponse(content_type='text/csv')
|
|
||||||
response['Content-Disposition'] = 'attachment; filename={}.csv'.format(meta)
|
|
||||||
writer = csv.writer(response)
|
|
||||||
|
|
||||||
writer.writerow(field_names)
|
|
||||||
for obj in queryset:
|
|
||||||
row = writer.writerow([getattr(obj, field) for field in field_names])
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
export_as_csv.short_description = _("Ausgewählte User exportieren")
|
export_as_csv.short_description = _("Ausgewählte User exportieren")
|
||||||
@@ -71,7 +86,7 @@ def _reported_content_link(obj):
|
|||||||
|
|
||||||
|
|
||||||
@admin.register(ReportComment)
|
@admin.register(ReportComment)
|
||||||
class ReportCommentAdmin(admin.ModelAdmin):
|
class ReportCommentAdmin(SimpleHistoryAdmin):
|
||||||
list_display = ["user_comment", "reported_content_link"]
|
list_display = ["user_comment", "reported_content_link"]
|
||||||
date_hierarchy = "created_at"
|
date_hierarchy = "created_at"
|
||||||
|
|
||||||
@@ -82,7 +97,7 @@ class ReportCommentAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
|
|
||||||
@admin.register(ReportAdoptionNotice)
|
@admin.register(ReportAdoptionNotice)
|
||||||
class ReportAdoptionNoticeAdmin(admin.ModelAdmin):
|
class ReportAdoptionNoticeAdmin(SimpleHistoryAdmin):
|
||||||
list_display = ["user_comment", "reported_content_link"]
|
list_display = ["user_comment", "reported_content_link"]
|
||||||
date_hierarchy = "created_at"
|
date_hierarchy = "created_at"
|
||||||
|
|
||||||
@@ -97,7 +112,7 @@ class SpeciesSpecificURLInline(admin.StackedInline):
|
|||||||
|
|
||||||
|
|
||||||
@admin.register(RescueOrganization)
|
@admin.register(RescueOrganization)
|
||||||
class RescueOrganizationAdmin(admin.ModelAdmin):
|
class RescueOrganizationAdmin(SimpleHistoryAdmin):
|
||||||
search_fields = ("name", "description", "internal_comment", "location_string", "location__city")
|
search_fields = ("name", "description", "internal_comment", "location_string", "location__city")
|
||||||
list_display = ("name", "trusted", "allows_using_materials", "website")
|
list_display = ("name", "trusted", "allows_using_materials", "website")
|
||||||
list_filter = ("allows_using_materials", "trusted", ("external_source_identifier", EmptyFieldListFilter))
|
list_filter = ("allows_using_materials", "trusted", ("external_source_identifier", EmptyFieldListFilter))
|
||||||
@@ -108,12 +123,12 @@ class RescueOrganizationAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
|
|
||||||
@admin.register(Text)
|
@admin.register(Text)
|
||||||
class TextAdmin(admin.ModelAdmin):
|
class TextAdmin(SimpleHistoryAdmin):
|
||||||
search_fields = ("title__icontains", "text_code__icontains",)
|
search_fields = ("title__icontains", "text_code__icontains",)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Comment)
|
@admin.register(Comment)
|
||||||
class CommentAdmin(admin.ModelAdmin):
|
class CommentAdmin(SimpleHistoryAdmin):
|
||||||
list_filter = ("user",)
|
list_filter = ("user",)
|
||||||
|
|
||||||
|
|
||||||
@@ -123,7 +138,7 @@ class BaseNotificationAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
|
|
||||||
@admin.register(SearchSubscription)
|
@admin.register(SearchSubscription)
|
||||||
class SearchSubscriptionAdmin(admin.ModelAdmin):
|
class SearchSubscriptionAdmin(SimpleHistoryAdmin):
|
||||||
list_filter = ("owner",)
|
list_filter = ("owner",)
|
||||||
|
|
||||||
|
|
||||||
@@ -151,8 +166,17 @@ class IsImportantListFilter(admin.SimpleListFilter):
|
|||||||
|
|
||||||
|
|
||||||
@admin.register(Location)
|
@admin.register(Location)
|
||||||
class LocationAdmin(admin.ModelAdmin):
|
class LocationAdmin(SimpleHistoryAdmin):
|
||||||
search_fields = ("name__icontains", "city__icontains")
|
search_fields = ("name__icontains", "city__icontains")
|
||||||
|
list_display = ("name", "city", "slug")
|
||||||
|
|
||||||
|
@admin.display(description=_("Slug"))
|
||||||
|
def slug(self, obj):
|
||||||
|
if obj.importantlocation:
|
||||||
|
return obj.importantlocation.slug
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
list_filter = [IsImportantListFilter]
|
list_filter = [IsImportantListFilter]
|
||||||
inlines = [
|
inlines = [
|
||||||
ImportantLocationInline,
|
ImportantLocationInline,
|
||||||
@@ -160,17 +184,39 @@ class LocationAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
|
|
||||||
@admin.register(SocialMediaPost)
|
@admin.register(SocialMediaPost)
|
||||||
class SocialMediaPostAdmin(admin.ModelAdmin):
|
class SocialMediaPostAdmin(SimpleHistoryAdmin):
|
||||||
list_filter = ("platform",)
|
list_filter = ("platform",)
|
||||||
|
|
||||||
|
|
||||||
admin.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(Species)
|
||||||
admin.site.register(Rule)
|
admin.site.register(Rule, SimpleHistoryAdmin)
|
||||||
admin.site.register(Image)
|
admin.site.register(Image)
|
||||||
admin.site.register(ModerationAction)
|
admin.site.register(ModerationAction, SimpleHistoryAdmin)
|
||||||
admin.site.register(Language)
|
admin.site.register(Language)
|
||||||
admin.site.register(Announcement)
|
admin.site.register(Announcement, SimpleHistoryAdmin)
|
||||||
admin.site.register(Subscriptions)
|
admin.site.register(Subscriptions, SimpleHistoryAdmin)
|
||||||
admin.site.register(Log)
|
|
||||||
admin.site.register(Timestamp)
|
admin.site.register(Timestamp)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from ..models import Animal, RescueOrganization, AdoptionNotice, Species, Image, Location
|
from ..models import Animal, RescueOrganization, AdoptionNotice, Species, Image, Location, SpeciesSpecificURL
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
import math
|
import math
|
||||||
|
|
||||||
@@ -144,10 +144,26 @@ class AnimalGetSerializer(serializers.ModelSerializer):
|
|||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class SpeciesSpecificURLSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = SpeciesSpecificURL
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
class RescueOrganizationSerializer(serializers.ModelSerializer):
|
class RescueOrganizationSerializer(serializers.ModelSerializer):
|
||||||
|
url = serializers.SerializerMethodField()
|
||||||
|
species_specific_urls = SpeciesSpecificURLSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RescueOrganization
|
model = RescueOrganization
|
||||||
exclude = ["internal_comment", "allows_using_materials"]
|
fields = ('id', 'name', 'url', 'trusted', 'location_string', 'instagram', "facebook", "fediverse_profile",
|
||||||
|
"email", "phone_number", "website", "updated_at", "created_at", "last_checked", "description",
|
||||||
|
"external_source_identifier", "external_object_identifier", "exclude_from_check",
|
||||||
|
"regular_check_status", "ongoing_communication", "twenty_id", "location", "parent_org", "specializations",
|
||||||
|
"species_specific_urls")
|
||||||
|
|
||||||
|
def get_url(self, obj):
|
||||||
|
return obj.get_absolute_url()
|
||||||
|
|
||||||
|
|
||||||
class ImageCreateSerializer(serializers.ModelSerializer):
|
class ImageCreateSerializer(serializers.ModelSerializer):
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ from django.urls import path
|
|||||||
from .views import (
|
from .views import (
|
||||||
AdoptionNoticeApiView,
|
AdoptionNoticeApiView,
|
||||||
AnimalApiView, RescueOrganizationApiView, AddImageApiView, SpeciesApiView, LocationApiView,
|
AnimalApiView, RescueOrganizationApiView, AddImageApiView, SpeciesApiView, LocationApiView,
|
||||||
AdoptionNoticeGeoJSONView, RescueOrgGeoJSONView, AdoptionNoticePerOrgApiView
|
AdoptionNoticeGeoJSONView, RescueOrgGeoJSONView, AdoptionNoticePerOrgApiView, index
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
path("", index, name="api-base-url"),
|
||||||
path("adoption_notice", AdoptionNoticeApiView.as_view(), name="api-adoption-notice-list"),
|
path("adoption_notice", AdoptionNoticeApiView.as_view(), name="api-adoption-notice-list"),
|
||||||
path("adoption_notice.geojson", AdoptionNoticeGeoJSONView.as_view(), name="api-adoption-notice-list-geojson"),
|
path("adoption_notice.geojson", AdoptionNoticeGeoJSONView.as_view(), name="api-adoption-notice-list-geojson"),
|
||||||
path("adoption_notice/<int:id>/", AdoptionNoticeApiView.as_view(), name="api-adoption-notice-detail"),
|
path("adoption_notice/<int:id>/", AdoptionNoticeApiView.as_view(), name="api-adoption-notice-detail"),
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.urls import reverse
|
||||||
from drf_spectacular.types import OpenApiTypes
|
from drf_spectacular.types import OpenApiTypes
|
||||||
from rest_framework.generics import ListAPIView
|
from rest_framework.generics import ListAPIView
|
||||||
|
|
||||||
@@ -450,3 +452,7 @@ class AdoptionNoticePerOrgApiView(APIView):
|
|||||||
adoption_notices = temporary_an_storage
|
adoption_notices = temporary_an_storage
|
||||||
serializer = AdoptionNoticeSerializer(adoption_notices, many=True, context={"request": request})
|
serializer = AdoptionNoticeSerializer(adoption_notices, many=True, context={"request": request})
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
def index(request):
|
||||||
|
return redirect(reverse("swagger-ui"))
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
|
from django.forms.widgets import Textarea
|
||||||
|
|
||||||
from .models import AdoptionNotice, Animal, Image, ReportAdoptionNotice, ReportComment, ModerationAction, User, Species, \
|
from .models import AdoptionNotice, Animal, Image, ReportAdoptionNotice, ReportComment, ModerationAction, User, Species, \
|
||||||
Comment, SexChoicesWithAll, DistanceChoices, SpeciesSpecificURL, RescueOrganization
|
Comment, SexChoicesWithAll, DistanceChoices, SpeciesSpecificURL, RescueOrganization
|
||||||
from django_registration.forms import RegistrationForm
|
from django_registration.forms import RegistrationForm
|
||||||
from crispy_forms.helper import FormHelper
|
from crispy_forms.helper import FormHelper
|
||||||
from crispy_forms.layout import Submit, Layout, Fieldset, HTML, Row, Column, Field, Hidden
|
from crispy_forms.layout import Submit, Layout, Fieldset
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from notfellchen.settings import MEDIA_URL
|
|
||||||
from crispy_forms.layout import Div
|
from crispy_forms.layout import Div
|
||||||
|
|
||||||
|
from .tools.model_helpers import reason_for_signup_label, reason_for_signup_help_text, AdoptionNoticeStatusChoices
|
||||||
|
|
||||||
|
|
||||||
def animal_validator(value: str):
|
def animal_validator(value: str):
|
||||||
value = value.lower()
|
value = value.lower()
|
||||||
animal_list = ["ratte", "farbratte", "katze", "hund", "kaninchen", "hase", "kuh", "fuchs", "cow", "rat", "cat",
|
animal_list = ["ratte", "farbratte", "katze", "hund", "kaninchen", "hase", "kuh", "fuchs", "cow", "rat", "cat",
|
||||||
"dog", "rabbit", "fox", "fancy rat"]
|
"dog", "rabbit", "fox", "fancy rat", "meerschweinchen"]
|
||||||
if value not in animal_list:
|
if value not in animal_list:
|
||||||
raise forms.ValidationError(_("Dieses Tier kenne ich nicht. Probier ein anderes"))
|
raise forms.ValidationError(_("Dieses Tier kenne ich nicht. Probier ein anderes"))
|
||||||
|
|
||||||
@@ -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):
|
class ImageForm(forms.ModelForm):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
if 'in_flow' in kwargs:
|
if 'in_flow' in kwargs:
|
||||||
@@ -105,6 +115,12 @@ class ReportCommentForm(forms.ModelForm):
|
|||||||
fields = ('reported_broken_rules', 'user_comment')
|
fields = ('reported_broken_rules', 'user_comment')
|
||||||
|
|
||||||
|
|
||||||
|
class UserModCommentForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = ('mod_notes',)
|
||||||
|
|
||||||
|
|
||||||
class CommentForm(forms.ModelForm):
|
class CommentForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Comment
|
model = Comment
|
||||||
@@ -129,6 +145,18 @@ class ModerationActionForm(forms.ModelForm):
|
|||||||
fields = ('action', 'public_comment', 'private_comment')
|
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 CustomRegistrationForm(RegistrationForm):
|
||||||
class Meta(RegistrationForm.Meta):
|
class Meta(RegistrationForm.Meta):
|
||||||
model = User
|
model = User
|
||||||
@@ -155,3 +183,31 @@ class RescueOrgSearchForm(forms.Form):
|
|||||||
location_string = forms.CharField(max_length=100, label=_("Stadt"), required=False)
|
location_string = forms.CharField(max_length=100, label=_("Stadt"), required=False)
|
||||||
max_distance = forms.ChoiceField(choices=DistanceChoices, initial=DistanceChoices.TWENTY,
|
max_distance = forms.ChoiceField(choices=DistanceChoices, initial=DistanceChoices.TWENTY,
|
||||||
label=_("Suchradius"))
|
label=_("Suchradius"))
|
||||||
|
|
||||||
|
|
||||||
|
class RescueOrgSearchByNameForm(forms.Form):
|
||||||
|
template_name = "fellchensammlung/forms/form_snippets.html"
|
||||||
|
name = forms.CharField(max_length=100, label=_("Name der Organisation"), required=False)
|
||||||
|
|
||||||
|
|
||||||
|
class CloseAdoptionNoticeForm(forms.ModelForm):
|
||||||
|
template_name = "fellchensammlung/forms/form_snippets.html"
|
||||||
|
|
||||||
|
adoption_notice_status = forms.ChoiceField(choices=AdoptionNoticeStatusChoices.Closed,
|
||||||
|
label=_("Status"),
|
||||||
|
help_text=_("Gib den neuen Status der Vermittlung an"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = AdoptionNotice
|
||||||
|
fields = ('adoption_notice_status',)
|
||||||
|
|
||||||
|
|
||||||
|
class RescueOrgForm(forms.ModelForm):
|
||||||
|
template_name = "fellchensammlung/forms/form_snippets.html"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = RescueOrganization
|
||||||
|
fields = ('name', 'allows_using_materials', 'location_string', "email", "phone_number", "website", "instagram",
|
||||||
|
"facebook", "fediverse_profile", "internal_comment", "description", "external_source_identifier",
|
||||||
|
"external_object_identifier",
|
||||||
|
"parent_org")
|
||||||
|
|||||||
13
src/fellchensammlung/management/commands/print-settings.py
Normal file
@@ -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.contrib.auth.models import AbstractUser
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
import base64
|
import base64
|
||||||
|
from simple_history.models import HistoricalRecords
|
||||||
|
|
||||||
from .tools import misc, geo
|
from .tools import misc, geo
|
||||||
from notfellchen.settings import MEDIA_URL, base_url
|
from notfellchen.settings import MEDIA_URL, base_url
|
||||||
from .tools.geo import LocationProxy, Position
|
from .tools.geo import LocationProxy, Position
|
||||||
from .tools.misc import time_since_as_hr_string
|
from .tools.misc import time_since_as_hr_string
|
||||||
from .tools.model_helpers import NotificationTypeChoices, AdoptionNoticeStatusChoices, AdoptionProcess, \
|
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
|
from .tools.model_helpers import ndm as NotificationDisplayMapping
|
||||||
|
|
||||||
|
|
||||||
@@ -120,11 +122,11 @@ class ExternalSourceChoices(models.TextChoices):
|
|||||||
|
|
||||||
|
|
||||||
class AllowUseOfMaterialsChices(models.TextChoices):
|
class AllowUseOfMaterialsChices(models.TextChoices):
|
||||||
USE_MATERIALS_ALLOWED = "allowed", _("Usage allowed")
|
USE_MATERIALS_ALLOWED = "allowed", _("Nutzung erlaubt")
|
||||||
USE_MATERIALS_REQUESTED = "requested", _("Usage requested")
|
USE_MATERIALS_REQUESTED = "requested", _("Nutzung angefragt")
|
||||||
USE_MATERIALS_DENIED = "denied", _("Usage denied")
|
USE_MATERIALS_DENIED = "denied", _("Nutzung verweigert")
|
||||||
USE_MATERIALS_OTHER = "other", _("It's complicated")
|
USE_MATERIALS_OTHER = "other", _("Es ist kompliziert")
|
||||||
USE_MATERIALS_NOT_ASKED = "not_asked", _("Not asked")
|
USE_MATERIALS_NOT_ASKED = "not_asked", _("Nutzung noch nicht angefragt")
|
||||||
|
|
||||||
|
|
||||||
class Species(models.Model):
|
class Species(models.Model):
|
||||||
@@ -165,21 +167,33 @@ class RescueOrganization(models.Model):
|
|||||||
internal_comment = models.TextField(verbose_name=_("Interner Kommentar"), null=True, blank=True, )
|
internal_comment = models.TextField(verbose_name=_("Interner Kommentar"), null=True, blank=True, )
|
||||||
description = models.TextField(null=True, blank=True, verbose_name=_('Beschreibung')) # Markdown allowed
|
description = models.TextField(null=True, blank=True, verbose_name=_('Beschreibung')) # Markdown allowed
|
||||||
external_object_identifier = models.CharField(max_length=200, null=True, blank=True,
|
external_object_identifier = models.CharField(max_length=200, null=True, blank=True,
|
||||||
verbose_name=_('External Object Identifier'))
|
verbose_name=_('External Object Identifier'),
|
||||||
|
help_text=_(
|
||||||
|
"Id des Objekts in der externen Datenbank (kann leer gelassen werden)"))
|
||||||
external_source_identifier = models.CharField(max_length=200, null=True, blank=True,
|
external_source_identifier = models.CharField(max_length=200, null=True, blank=True,
|
||||||
choices=ExternalSourceChoices.choices,
|
choices=ExternalSourceChoices.choices,
|
||||||
verbose_name=_('External Source Identifier'))
|
verbose_name=_('External Source Identifier'),
|
||||||
|
help_text=_(
|
||||||
|
"Name der Datenbank aus der die Tierschutzorganisation importiert wurde (kann leer gelassen werden)"))
|
||||||
exclude_from_check = models.BooleanField(default=False, verbose_name=_('Von Prüfung ausschließen'),
|
exclude_from_check = models.BooleanField(default=False, verbose_name=_('Von Prüfung ausschließen'),
|
||||||
help_text=_("Organisation von der manuellen Überprüfung ausschließen, "
|
help_text=_("Organisation von der manuellen Überprüfung ausschließen, "
|
||||||
"z.B. weil Tiere nicht online geführt werden"))
|
"z.B. weil Tiere nicht online geführt werden"))
|
||||||
|
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'),
|
ongoing_communication = models.BooleanField(default=False, verbose_name=_('In aktiver Kommunikation'),
|
||||||
help_text=_(
|
help_text=_(
|
||||||
"Es findet gerade Kommunikation zwischen Notfellchen und der Organisation statt."))
|
"Es findet gerade Kommunikation zwischen Notfellchen und der Organisation statt."))
|
||||||
parent_org = models.ForeignKey("RescueOrganization", on_delete=models.PROTECT, blank=True, null=True)
|
parent_org = models.ForeignKey("RescueOrganization", on_delete=models.PROTECT, blank=True, null=True,
|
||||||
|
verbose_name=_("Übergeordnete Organisation"))
|
||||||
# allows to specify if a rescue organization has a specialization for dedicated species
|
# allows to specify if a rescue organization has a specialization for dedicated species
|
||||||
specializations = models.ManyToManyField(Species, blank=True)
|
specializations = models.ManyToManyField(Species, blank=True)
|
||||||
twenty_id = models.UUIDField(verbose_name=_("Twenty-ID"), null=True, blank=True,
|
twenty_id = models.UUIDField(verbose_name=_("Twenty-ID"), null=True, blank=True,
|
||||||
help_text=_("ID der der Organisation in Twenty"))
|
help_text=_("ID der der Organisation in Twenty"))
|
||||||
|
history = HistoricalRecords()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('external_object_identifier', 'external_source_identifier',)
|
unique_together = ('external_object_identifier', 'external_source_identifier',)
|
||||||
@@ -243,6 +257,7 @@ class RescueOrganization(models.Model):
|
|||||||
|
|
||||||
def set_checked(self):
|
def set_checked(self):
|
||||||
self.last_checked = timezone.now()
|
self.last_checked = timezone.now()
|
||||||
|
self._change_reason = 'Organization checked'
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -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
|
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
|
@property
|
||||||
def child_organizations(self):
|
def child_organizations(self):
|
||||||
return RescueOrganization.objects.filter(parent_org=self)
|
return RescueOrganization.objects.filter(parent_org=self)
|
||||||
@@ -305,9 +316,10 @@ class User(AbstractUser):
|
|||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
organization_affiliation = models.ForeignKey(RescueOrganization, on_delete=models.PROTECT, null=True, blank=True,
|
organization_affiliation = models.ForeignKey(RescueOrganization, on_delete=models.PROTECT, null=True, blank=True,
|
||||||
verbose_name=_('Organisation'))
|
verbose_name=_('Organisation'))
|
||||||
reason_for_signup = models.TextField(verbose_name=_("Grund für die Registrierung"), help_text=_(
|
reason_for_signup = models.TextField(verbose_name=reason_for_signup_label, help_text=reason_for_signup_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."))
|
mod_notes = models.TextField(verbose_name=_("Moderationsnotizen"), null=True, blank=True)
|
||||||
email_notifications = models.BooleanField(verbose_name=_("Benachrichtigung per E-Mail"), default=True)
|
email_notifications = models.BooleanField(verbose_name=_("Benachrichtigung per E-Mail"), default=True)
|
||||||
|
history = HistoricalRecords()
|
||||||
REQUIRED_FIELDS = ["reason_for_signup", "email"]
|
REQUIRED_FIELDS = ["reason_for_signup", "email"]
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -403,6 +415,7 @@ class AdoptionNotice(models.Model):
|
|||||||
adoption_process = models.TextField(null=True, blank=True,
|
adoption_process = models.TextField(null=True, blank=True,
|
||||||
max_length=64, verbose_name=_('Adoptionsprozess'),
|
max_length=64, verbose_name=_('Adoptionsprozess'),
|
||||||
choices=AdoptionProcess)
|
choices=AdoptionProcess)
|
||||||
|
history = HistoricalRecords()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def animals(self):
|
def animals(self):
|
||||||
@@ -419,7 +432,7 @@ class AdoptionNotice(models.Model):
|
|||||||
def num_per_sex(self):
|
def num_per_sex(self):
|
||||||
num_per_sex = dict()
|
num_per_sex = dict()
|
||||||
for sex in SexChoices:
|
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
|
return num_per_sex
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -509,6 +522,7 @@ class AdoptionNotice(models.Model):
|
|||||||
photos.extend(animal.photos.all())
|
photos.extend(animal.photos.all())
|
||||||
if len(photos) > 0:
|
if len(photos) > 0:
|
||||||
return photos
|
return photos
|
||||||
|
return None
|
||||||
|
|
||||||
def get_photo(self):
|
def get_photo(self):
|
||||||
"""
|
"""
|
||||||
@@ -538,6 +552,13 @@ class AdoptionNotice(models.Model):
|
|||||||
def _values_of(list_of_enums):
|
def _values_of(list_of_enums):
|
||||||
return list(map(lambda x: x[0], list_of_enums))
|
return list(map(lambda x: x[0], list_of_enums))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status_category(self):
|
||||||
|
ansc = AdoptionNoticeStatusChoices
|
||||||
|
for status_category in [ansc.Active, ansc.Disabled, ansc.Closed, ansc.AwaitingAction]:
|
||||||
|
if self.adoption_notice_status in self._values_of(status_category.choices):
|
||||||
|
return status_category.__name__.lower()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_active(self):
|
def is_active(self):
|
||||||
return self.adoption_notice_status in self._values_of(AdoptionNoticeStatusChoices.Active.choices)
|
return self.adoption_notice_status in self._values_of(AdoptionNoticeStatusChoices.Active.choices)
|
||||||
@@ -554,6 +575,19 @@ class AdoptionNotice(models.Model):
|
|||||||
def is_awaiting_action(self):
|
def is_awaiting_action(self):
|
||||||
return self.adoption_notice_status in self._values_of(AdoptionNoticeStatusChoices.AwaitingAction.choices)
|
return self.adoption_notice_status in self._values_of(AdoptionNoticeStatusChoices.AwaitingAction.choices)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status_description_short(self):
|
||||||
|
if self.is_active:
|
||||||
|
return _("Vermittlung aktiv")
|
||||||
|
elif self.is_disabled:
|
||||||
|
return _("Vermittlung gesperrt")
|
||||||
|
elif self.is_closed:
|
||||||
|
return _("Vermittlung geschlossen")
|
||||||
|
elif self.is_awaiting_action:
|
||||||
|
return _("Wartet auf Freigabe von Moderator*innen")
|
||||||
|
else:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def status_description(self):
|
def status_description(self):
|
||||||
return AdoptionNoticeStatusChoicesDescriptions.mapping[self.adoption_notice_status]
|
return AdoptionNoticeStatusChoicesDescriptions.mapping[self.adoption_notice_status]
|
||||||
@@ -603,7 +637,7 @@ class Animal(models.Model):
|
|||||||
verbose_name = _('Tier')
|
verbose_name = _('Tier')
|
||||||
verbose_name_plural = _('Tiere')
|
verbose_name_plural = _('Tiere')
|
||||||
|
|
||||||
date_of_birth = models.DateField(verbose_name=_('Geburtsdatum'))
|
date_of_birth = models.DateField(verbose_name=_('Geburtsdatum'), null=True, blank=True)
|
||||||
name = models.CharField(max_length=200, verbose_name=_('Name'))
|
name = models.CharField(max_length=200, verbose_name=_('Name'))
|
||||||
description = models.TextField(null=True, blank=True, verbose_name=_('Beschreibung'))
|
description = models.TextField(null=True, blank=True, verbose_name=_('Beschreibung'))
|
||||||
species = models.ForeignKey(Species, on_delete=models.PROTECT, verbose_name=_("Tierart"))
|
species = models.ForeignKey(Species, on_delete=models.PROTECT, verbose_name=_("Tierart"))
|
||||||
@@ -617,18 +651,25 @@ class Animal(models.Model):
|
|||||||
owner = models.ForeignKey(User, on_delete=models.CASCADE)
|
owner = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
history = HistoricalRecords()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.name}"
|
return f"{self.name}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def age(self):
|
def age(self):
|
||||||
return timezone.now().today().date() - self.date_of_birth
|
if self.date_of_birth:
|
||||||
|
return timezone.now().today().date() - self.date_of_birth
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hr_age(self):
|
def hr_age(self):
|
||||||
"""Returns a human-readable age based on the date of birth."""
|
"""Returns a human-readable age based on the date of birth."""
|
||||||
return misc.age_as_hr_string(self.age)
|
if self.date_of_birth:
|
||||||
|
return misc.age_as_hr_string(self.age)
|
||||||
|
else:
|
||||||
|
return _("Unbekannt")
|
||||||
|
|
||||||
def get_photo(self):
|
def get_photo(self):
|
||||||
"""
|
"""
|
||||||
@@ -680,6 +721,7 @@ class SearchSubscription(models.Model):
|
|||||||
max_distance = models.IntegerField(choices=DistanceChoices.choices, null=True)
|
max_distance = models.IntegerField(choices=DistanceChoices.choices, null=True)
|
||||||
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Zuletzt geändert am"))
|
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Zuletzt geändert am"))
|
||||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Erstellt am"))
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Erstellt am"))
|
||||||
|
history = HistoricalRecords()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.location and self.max_distance:
|
if self.location and self.max_distance:
|
||||||
@@ -710,6 +752,7 @@ class Rule(models.Model):
|
|||||||
"Identifikator haben"))
|
"Identifikator haben"))
|
||||||
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Zuletzt geändert am"))
|
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Zuletzt geändert am"))
|
||||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Erstellt am"))
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Erstellt am"))
|
||||||
|
history = HistoricalRecords()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
@@ -735,6 +778,7 @@ class Report(models.Model):
|
|||||||
user_comment = models.TextField(blank=True, verbose_name=_("Kommentar/Zusätzliche Information"))
|
user_comment = models.TextField(blank=True, verbose_name=_("Kommentar/Zusätzliche Information"))
|
||||||
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Zuletzt geändert am"))
|
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Zuletzt geändert am"))
|
||||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Erstellt am"))
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Erstellt am"))
|
||||||
|
history = HistoricalRecords()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"[{self.status}]: {self.user_comment:.20}"
|
return f"[{self.status}]: {self.user_comment:.20}"
|
||||||
@@ -821,6 +865,7 @@ class ModerationAction(models.Model):
|
|||||||
# Only visible to moderator
|
# Only visible to moderator
|
||||||
private_comment = models.TextField(blank=True)
|
private_comment = models.TextField(blank=True)
|
||||||
report = models.ForeignKey(Report, on_delete=models.CASCADE)
|
report = models.ForeignKey(Report, on_delete=models.CASCADE)
|
||||||
|
history = HistoricalRecords()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"[{self.action}]: {self.public_comment}"
|
return f"[{self.action}]: {self.public_comment}"
|
||||||
@@ -842,6 +887,7 @@ class Text(models.Model):
|
|||||||
content = models.TextField(verbose_name="Inhalt")
|
content = models.TextField(verbose_name="Inhalt")
|
||||||
language = models.ForeignKey(Language, verbose_name="Sprache", on_delete=models.PROTECT)
|
language = models.ForeignKey(Language, verbose_name="Sprache", on_delete=models.PROTECT)
|
||||||
text_code = models.CharField(max_length=24, verbose_name="Text code", blank=True)
|
text_code = models.CharField(max_length=24, verbose_name="Text code", blank=True)
|
||||||
|
history = HistoricalRecords()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Text"
|
verbose_name = "Text"
|
||||||
@@ -885,6 +931,7 @@ class Announcement(Text):
|
|||||||
INFO: "info",
|
INFO: "info",
|
||||||
}
|
}
|
||||||
type = models.CharField(choices=TYPES, max_length=100, default=INFO)
|
type = models.CharField(choices=TYPES, max_length=100, default=INFO)
|
||||||
|
history = HistoricalRecords()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_active(self):
|
def is_active(self):
|
||||||
@@ -931,6 +978,7 @@ class Comment(models.Model):
|
|||||||
adoption_notice = models.ForeignKey(AdoptionNotice, on_delete=models.CASCADE, verbose_name=_('Vermittlung'))
|
adoption_notice = models.ForeignKey(AdoptionNotice, on_delete=models.CASCADE, verbose_name=_('Vermittlung'))
|
||||||
text = models.TextField(verbose_name="Inhalt")
|
text = models.TextField(verbose_name="Inhalt")
|
||||||
reply_to = models.ForeignKey("self", verbose_name="Antwort auf", blank=True, null=True, on_delete=models.CASCADE)
|
reply_to = models.ForeignKey("self", verbose_name="Antwort auf", blank=True, null=True, on_delete=models.CASCADE)
|
||||||
|
history = HistoricalRecords()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.user} at {self.created_at.strftime('%H:%M %d.%m.%y')}: {self.text:.10}"
|
return f"{self.user} at {self.created_at.strftime('%H:%M %d.%m.%y')}: {self.text:.10}"
|
||||||
@@ -1002,6 +1050,7 @@ class Subscriptions(models.Model):
|
|||||||
help_text=_("Vermittlung die abonniert wurde"))
|
help_text=_("Vermittlung die abonniert wurde"))
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
history = HistoricalRecords()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.owner} - {self.adoption_notice}"
|
return f"{self.owner} - {self.adoption_notice}"
|
||||||
@@ -1048,8 +1097,10 @@ class SpeciesSpecificURL(models.Model):
|
|||||||
verbose_name_plural = _("Tierartspezifische URLs")
|
verbose_name_plural = _("Tierartspezifische URLs")
|
||||||
|
|
||||||
species = models.ForeignKey(Species, on_delete=models.CASCADE, verbose_name=_("Tierart"))
|
species = models.ForeignKey(Species, on_delete=models.CASCADE, verbose_name=_("Tierart"))
|
||||||
rescue_organization = models.ForeignKey(RescueOrganization, on_delete=models.CASCADE,
|
rescue_organization = models.ForeignKey(RescueOrganization,
|
||||||
verbose_name=_("Tierschutzorganisation"))
|
on_delete=models.CASCADE,
|
||||||
|
verbose_name=_("Tierschutzorganisation"),
|
||||||
|
related_name="species_specific_urls")
|
||||||
url = models.URLField(verbose_name=_("Tierartspezifische URL"))
|
url = models.URLField(verbose_name=_("Tierartspezifische URL"))
|
||||||
|
|
||||||
|
|
||||||
@@ -1063,6 +1114,7 @@ class SocialMediaPost(models.Model):
|
|||||||
choices=PlatformChoices.choices)
|
choices=PlatformChoices.choices)
|
||||||
adoption_notice = models.ForeignKey(AdoptionNotice, on_delete=models.CASCADE, verbose_name=_('Vermittlung'))
|
adoption_notice = models.ForeignKey(AdoptionNotice, on_delete=models.CASCADE, verbose_name=_('Vermittlung'))
|
||||||
url = models.URLField(verbose_name=_("URL"))
|
url = models.URLField(verbose_name=_("URL"))
|
||||||
|
history = HistoricalRecords()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_an_to_post():
|
def get_an_to_post():
|
||||||
|
|||||||
@@ -22,12 +22,24 @@ $confirm: hsl(133deg, 100%, calc(41% + 0%));
|
|||||||
background-color: $grey-light !important;
|
background-color: $grey-light !important;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navbar-burger {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
background-color: $grey-dark;
|
background-color: $grey-dark;
|
||||||
}
|
}
|
||||||
a.card-footer-item.is-danger {
|
div.card-footer-item.is-danger {
|
||||||
color: black;
|
background-color: #5a212d;
|
||||||
}
|
}
|
||||||
|
div.card-footer-item.is-warning {
|
||||||
|
background-color: #523e13;
|
||||||
|
}
|
||||||
|
div.card-footer-item.is-confirm {
|
||||||
|
background-color: #00420f;
|
||||||
|
}
|
||||||
|
|
||||||
.tag {
|
.tag {
|
||||||
color: $grey-dark;
|
color: $grey-dark;
|
||||||
background-color: $grey-light;
|
background-color: $grey-light;
|
||||||
@@ -245,6 +257,34 @@ IMAGES
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cover {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(192, 192, 192, 0.75);
|
||||||
|
z-index: 99;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 500ms;
|
||||||
|
border-radius: inherit; // ensure border radius of cards is followed, see https://css-tricks.com/preventing-child-background-overflow-with-inherited-border-radii/
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover:hover {
|
||||||
|
transition: opacity 500ms;
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
// For being able to click the image and for accessibility the cover should have display: none;
|
||||||
|
// This is accepted by W3C: https://front-end.social/@mia/109433817951030826
|
||||||
|
// However this does not yet seem to be supported to be animated by browsers.
|
||||||
|
// Chrome claims to have shipped it: https://chromestatus.com/feature/5154958272364544
|
||||||
|
// However I couldn't get it to work there too.
|
||||||
|
// display: none;
|
||||||
|
// transition: opacity 1000ms, display 100ms;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
AN Cards
|
AN Cards
|
||||||
@@ -322,11 +362,10 @@ AN Cards
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.notification-container {
|
.notification-container {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-label {
|
.notification-label {
|
||||||
@@ -335,16 +374,16 @@ AN Cards
|
|||||||
|
|
||||||
/* Make the badge float in the top right corner of the button */
|
/* Make the badge float in the top right corner of the button */
|
||||||
.notification-badge {
|
.notification-badge {
|
||||||
background-color: #fa3e3e;
|
background-color: #fa3e3e;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
color: white;
|
color: white;
|
||||||
|
|
||||||
padding: 1px 3px;
|
padding: 1px 3px;
|
||||||
font-size: 8px;
|
font-size: 8px;
|
||||||
|
|
||||||
position: absolute; /* Position the badge within the relatively positioned button */
|
position: absolute; /* Position the badge within the relatively positioned button */
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1 @@
|
|||||||
|
{% extends "fellchensammlung/base.html" %}
|
||||||
@@ -33,6 +33,11 @@
|
|||||||
<i class="fas fa-search fa-fw"></i> {% trans 'Suchen' %}
|
<i class="fas fa-search fa-fw"></i> {% trans 'Suchen' %}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
<hr>
|
||||||
|
<form method="post" autocomplete="off">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ org_name_search_form }}
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -69,4 +74,57 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
const $nameInput = $("#id_name");
|
||||||
|
|
||||||
|
$nameInput.wrap("<div class='dropdown' id='location-result-list'></div>");
|
||||||
|
const dropdown = $("#location-result-list");
|
||||||
|
$nameInput.wrap("<div class='dropdown-trigger'></div>");
|
||||||
|
$("<div class='dropdown-content' id='results'></div>").insertAfter($nameInput);
|
||||||
|
const $resultsList = $("#results");
|
||||||
|
$resultsList.wrap("<div class='dropdown-menu'></div>");
|
||||||
|
|
||||||
|
|
||||||
|
$nameInput.on("input", function () {
|
||||||
|
const query = $.trim($nameInput.val());
|
||||||
|
|
||||||
|
if (query.length < 3) {
|
||||||
|
dropdown.removeClass("is-active");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "{% api_base_url %}organizations/",
|
||||||
|
data: {
|
||||||
|
search: query
|
||||||
|
},
|
||||||
|
method: "GET",
|
||||||
|
dataType: "json",
|
||||||
|
success: function (data) {
|
||||||
|
$resultsList.empty();
|
||||||
|
dropdown.addClass("is-active");
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
const orgs = data.slice(0, 5);
|
||||||
|
|
||||||
|
$.each(orgs, function (index, org) {
|
||||||
|
const $listItem = $("<a>")
|
||||||
|
.addClass("dropdown-item")
|
||||||
|
.addClass("result-item")
|
||||||
|
.attr('href', org.url)
|
||||||
|
.text(org.name);
|
||||||
|
|
||||||
|
$resultsList.append($listItem);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function () {
|
||||||
|
$resultsList.html('<li class="result-item">{% trans 'Error fetching data. Please try again.' %}</li>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -37,6 +37,26 @@
|
|||||||
{% block header %}
|
{% block header %}
|
||||||
{% include "fellchensammlung/header.html" %}
|
{% include "fellchensammlung/header.html" %}
|
||||||
{% endblock %}
|
{% 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">
|
<div class="main-content">
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
@@ -45,5 +65,8 @@
|
|||||||
{% block footer %}
|
{% block footer %}
|
||||||
{% include "fellchensammlung/footer.html" %}
|
{% include "fellchensammlung/footer.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_body %}
|
||||||
|
{% endblock extra_body %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -114,7 +114,17 @@
|
|||||||
<i class="fas fa-chart-line fa-fw"></i> {% trans 'Aufrufe' %}
|
<i class="fas fa-chart-line fa-fw"></i> {% trans 'Aufrufe' %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<hr class="dropdown-divider">
|
<hr class="dropdown-divider">
|
||||||
|
|
||||||
|
<a class="dropdown-item"
|
||||||
|
href="{% url 'adoption-notice-social-media-template-selection' adoption_notice_id=adoption_notice.pk %}">
|
||||||
|
<i class="fab fa-instagram fa-fw"
|
||||||
|
aria-hidden="true"></i> {% trans 'Social Media Vorlagen' %}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<hr class="dropdown-divider">
|
||||||
|
|
||||||
<a class="dropdown-item is-warning"
|
<a class="dropdown-item is-warning"
|
||||||
href="{% url adoption_notice_meta|admin_urlname:'change' adoption_notice.pk %}">
|
href="{% url adoption_notice_meta|admin_urlname:'change' adoption_notice.pk %}">
|
||||||
<i class="fa-solid fa-tools fa-fw"></i> Admin interface
|
<i class="fa-solid fa-tools fa-fw"></i> Admin interface
|
||||||
@@ -164,6 +174,9 @@
|
|||||||
{% if adoption_notice.get_photos %}
|
{% if adoption_notice.get_photos %}
|
||||||
<div class="column block">
|
<div class="column block">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
{% if not adoption_notice.is_active %}
|
||||||
|
<div class="cover">{{ adoption_notice.status_description_short }}</div>
|
||||||
|
{% endif %}
|
||||||
<div class="grid card-content">
|
<div class="grid card-content">
|
||||||
<div class="gallery">
|
<div class="gallery">
|
||||||
{% with photo=adoption_notice.get_photos.0 %}
|
{% with photo=adoption_notice.get_photos.0 %}
|
||||||
|
|||||||
@@ -19,59 +19,25 @@
|
|||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<div class="card">
|
{% include "fellchensammlung/partials/rescue_orgs/partial-basic-info-card.html" %}
|
||||||
<div class="card-header">
|
</div>
|
||||||
<h1 class="card-header-title">{{ org.name }}</h1>
|
<div class="block">
|
||||||
</div>
|
{% include "fellchensammlung/partials/rescue_orgs/partial-rescue-organization-contact.html" %}
|
||||||
<div class="card-content">
|
</div>
|
||||||
<div class="block">
|
{% trust_level "MODERATOR" as coordinator_trust_level %}
|
||||||
<b><i class="fa-solid fa-location-dot"></i></b>
|
{% if request.user.trust_level >= coordinator_trust_level %}
|
||||||
{% if org.location %}
|
<div class="block">
|
||||||
{{ org.location }}
|
<a class="button is-primary is-fullwidth"
|
||||||
{% else %}
|
href="{% url 'rescue-organization-edit' rescue_organization_id=org.pk %}">
|
||||||
{{ org.location_string }}
|
<i class="fa-solid fa-tools fa-fw"></i> {% translate 'Bearbeiten' %}
|
||||||
{% endif %}
|
</a>
|
||||||
{% 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>
|
</div>
|
||||||
</div>
|
<div class="block">
|
||||||
<div class="block">
|
<a class="button is-warning is-fullwidth" href="{% url org_meta|admin_urlname:'change' org.pk %}">
|
||||||
{% include "fellchensammlung/partials/partial-rescue-organization-contact.html" %}
|
<i class="fa-solid fa-tools fa-fw"></i> Admin interface
|
||||||
</div>
|
</a>
|
||||||
<div class="block">
|
</div>
|
||||||
<a class="button is-warning is-fullwidth" href="{% url org_meta|admin_urlname:'change' org.pk %}">
|
{% endif %}
|
||||||
<i class="fa-solid fa-tools fa-fw"></i> Admin interface
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
{% include "fellchensammlung/partials/partial-map.html" %}
|
{% include "fellchensammlung/partials/partial-map.html" %}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{% extends "fellchensammlung/base.html" %}
|
{% extends "fellchensammlung/base.html" %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load account %}
|
||||||
|
|
||||||
{% block title %}<title>{{ user.get_full_name }}</title>{% endblock %}
|
{% block title %}<title>{% user_display user %}</title>{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
@@ -13,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="level-right">
|
<div class="level-right">
|
||||||
<div class="level-item">
|
<div class="level-item">
|
||||||
<form class="" action="{% url 'logout' %}" method="post">
|
<form class="" action="{% url 'account_logout' %}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button class="button" type="submit">
|
<button class="button" type="submit">
|
||||||
<i aria-hidden="true" class="fas fa-sign-out fa-fw"></i> Logout
|
<i aria-hidden="true" class="fas fa-sign-out fa-fw"></i> Logout
|
||||||
@@ -25,60 +26,99 @@
|
|||||||
|
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<h2 class="title is-2">{% trans 'Profil verwalten' %}</h2>
|
<h2 class="title is-2">{% trans 'Profil verwalten' %}</h2>
|
||||||
<p><strong>{% translate "E-Mail" %}:</strong> {{ user.email }}</p>
|
<div class="block"><strong>{% translate "E-Mail" %}:</strong> {{ user.email }}</div>
|
||||||
<div class="">
|
{% if user.id is request.user.id %}
|
||||||
<p>
|
<div class="block">
|
||||||
<a class="button is-warning" href="{% url 'password_change' %}">{% translate "Change password" %}</a>
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
<a class="button is-info" href="{% url 'user-me-export' %}">{% translate "Daten exportieren" %}</a>
|
<div class="control">
|
||||||
</p>
|
<a class="button is-warning"
|
||||||
</div>
|
href="{% url 'account_change_password' %}">{% translate "Passwort ändern" %}</a>
|
||||||
</div>
|
</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">
|
||||||
<div class="block">
|
<h2 class="title is-2">{% trans 'Einstellungen' %}</h2>
|
||||||
<h2 class="title is-2">{% trans 'Einstellungen' %}</h2>
|
<form class="block" action="" method="POST">
|
||||||
<form class="block" action="" method="POST">
|
{% csrf_token %}
|
||||||
{% csrf_token %}
|
{% if user.email_notifications %}
|
||||||
{% if user.email_notifications %}
|
<label class="toggle">
|
||||||
<label class="toggle">
|
<input type="submit" class="toggle-checkbox checked" name="toggle_email_notifications">
|
||||||
<input type="submit" class="toggle-checkbox checked" name="toggle_email_notifications">
|
<div class="toggle-switch round "></div>
|
||||||
<div class="toggle-switch round "></div>
|
<span class="slider-label">
|
||||||
<span class="slider-label">
|
|
||||||
{% translate 'E-Mail Benachrichtigungen' %}
|
{% translate 'E-Mail Benachrichtigungen' %}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
{% else %}
|
|
||||||
<label class="toggle">
|
|
||||||
<input type="submit" class="toggle-checkbox" name="toggle_email_notifications">
|
|
||||||
<div class="toggle-switch round"></div>
|
|
||||||
<span class="slider-label">
|
|
||||||
{% translate 'E-Mail Benachrichtigungen' %}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
|
||||||
<details>
|
|
||||||
<summary><strong>{% trans 'Erweiterte Einstellungen' %}</strong></summary>
|
|
||||||
<div class="block">
|
|
||||||
{% if token %}
|
|
||||||
<form action="" method="POST">
|
|
||||||
{% csrf_token %}
|
|
||||||
<p class="text-muted"><strong>{% translate "API token:" %}</strong> {{ token }}</p>
|
|
||||||
<input class="button is-danger" type="submit" name="delete_token"
|
|
||||||
value={% translate "Delete API token" %}>
|
|
||||||
</form>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>{% translate "Kein API-Token vorhanden." %}</p>
|
<label class="toggle">
|
||||||
<form action="" method="POST">
|
<input type="submit" class="toggle-checkbox" name="toggle_email_notifications">
|
||||||
{% csrf_token %}
|
<div class="toggle-switch round"></div>
|
||||||
<input class="button is-primary" type="submit" name="create_token"
|
<span class="slider-label">
|
||||||
value={% translate "Create API token" %}>
|
{% translate 'E-Mail Benachrichtigungen' %}
|
||||||
</form>
|
</span>
|
||||||
|
</label>
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
<details>
|
||||||
|
<summary><strong>{% trans 'Erweiterte Einstellungen' %}</strong></summary>
|
||||||
|
<div class="block">
|
||||||
|
{% if token %}
|
||||||
|
<form action="" method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<p class="text-muted"><strong>{% translate "API token:" %}</strong> {{ token }}</p>
|
||||||
|
<input class="button is-danger" type="submit" name="delete_token"
|
||||||
|
value={% translate "Delete API token" %}>
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<p>{% translate "Kein API-Token vorhanden." %}</p>
|
||||||
|
<form action="" method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input class="button is-primary" type="submit" name="create_token"
|
||||||
|
value={% translate "Create API token" %}>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</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 %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2 class="title is-2">{% translate 'Benachrichtigungen' %}</h2>
|
<h2 class="title is-2">{% translate 'Benachrichtigungen' %}</h2>
|
||||||
{% include "fellchensammlung/lists/list-notifications.html" %}
|
{% include "fellchensammlung/lists/list-notifications.html" %}
|
||||||
@@ -86,8 +126,7 @@
|
|||||||
<h2 class="title is-2">{% translate 'Abonnierte Suchen' %}</h2>
|
<h2 class="title is-2">{% translate 'Abonnierte Suchen' %}</h2>
|
||||||
{% include "fellchensammlung/lists/list-search-subscriptions.html" %}
|
{% include "fellchensammlung/lists/list-search-subscriptions.html" %}
|
||||||
|
|
||||||
<h2 class="title is-2">{% translate 'Meine Vermittlungen' %}</h2>
|
<h2 class="title is-2">{% translate 'Vermittlungen' %}</h2>
|
||||||
{% include "fellchensammlung/lists/list-adoption-notices.html" %}
|
{% include "fellchensammlung/lists/list-adoption-notices.html" %}
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -7,9 +7,14 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 class="title is-1">403 Forbidden</h1>
|
<h1 class="title is-1">403 Forbidden</h1>
|
||||||
<p>
|
<p>
|
||||||
{% blocktranslate %}
|
{% if error_message %}
|
||||||
Diese Aktion ist dir nicht erlaubt. Logge dich ein oder nutze einen anderen Account. Wenn du denkst, dass hier
|
{{ error_message }}
|
||||||
ein Fehler vorliegt, kontaktiere das Team!
|
{% else %}
|
||||||
{% endblocktranslate %}
|
{% blocktranslate %}
|
||||||
|
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>
|
</p>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load custom_tags %}
|
{% load custom_tags %}
|
||||||
|
|
||||||
{% block title %}<title>{% translate "403 Forbidden" %}</title>{% endblock %}
|
{% block title %}<title>{% translate "404 Forbidden" %}</title>{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 class="title is-1">404 Not Found</h1>
|
<h1 class="title is-1">404 Not Found</h1>
|
||||||
|
|||||||
@@ -102,6 +102,10 @@
|
|||||||
<a class="nav-link " href="{% url "modtools" %}">
|
<a class="nav-link " href="{% url "modtools" %}">
|
||||||
{% translate 'Moderationstools' %}
|
{% translate 'Moderationstools' %}
|
||||||
</a>
|
</a>
|
||||||
|
<br>
|
||||||
|
<a class="nav-link " href="{% url "rescue-organization-create" %}">
|
||||||
|
{% translate 'Tierschutzorganisation hinzufügen' %}
|
||||||
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<br/>
|
<br/>
|
||||||
{% if request.user.is_superuser %}
|
{% if request.user.is_superuser %}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{% extends "fellchensammlung/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block description %}
|
||||||
|
<meta name="description" content="{% trans 'User aktivieren' %}">
|
||||||
|
{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="title is-1">{% trans 'User aktivieren' %}</h1>
|
||||||
|
{% blocktranslate %}
|
||||||
|
Hier kannst du einen User manuell aktivieren und optional eine Notiz hinterlassen.
|
||||||
|
{% endblocktranslate %}
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form }}
|
||||||
|
<button class="button is-info is-fullwidth" type="submit">{% translate "Aktivieren" %}</button>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{% extends "fellchensammlung/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block description %}
|
||||||
|
<meta name="description" content="{% trans 'User deaktivieren' %}">
|
||||||
|
{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="title is-1">{% trans 'User deaktivieren' %}</h1>
|
||||||
|
{% url "about" as rule_url %}
|
||||||
|
{% blocktranslate %}
|
||||||
|
Wenn dieser User unseren <a href='{{ rule_url }}'>Regeln</a> zuwider gehandelt hat oder seit langem inaktiv
|
||||||
|
ist, kannst du ihn deaktivieren.
|
||||||
|
{% endblocktranslate %}
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form }}
|
||||||
|
<button class="button is-danger is-fullwidth" type="submit">{% translate "Deaktivieren" %}</button>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,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" }}
|
{{ field|add_class:"input" }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="help">
|
<div class="help content">
|
||||||
{{ field.help_text }}
|
{{ field.help_text }}
|
||||||
</div>
|
</div>
|
||||||
<div class="help is-danger">
|
<div class="help is-danger">
|
||||||
|
|||||||
@@ -49,10 +49,10 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<div class="navbar-item">
|
<div class="navbar-item">
|
||||||
<div class="buttons">
|
<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>
|
<strong>{% translate "Registrieren" %}</strong>
|
||||||
</a>
|
</a>
|
||||||
<a class="button is-light" href="{% url "login" %}">
|
<a class="button is-light" href="{% url "account_login" %}">
|
||||||
<strong>{% translate "Login" %}</strong>
|
<strong>{% translate "Login" %}</strong>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
|
After Width: | Height: | Size: 54 KiB |
@@ -3,7 +3,7 @@
|
|||||||
{% if rescue_organizations %}
|
{% if rescue_organizations %}
|
||||||
{% for rescue_organization in rescue_organizations %}
|
{% for rescue_organization in rescue_organizations %}
|
||||||
<div class="cell">
|
<div class="cell">
|
||||||
{% include "fellchensammlung/partials/partial-rescue-organization.html" %}
|
{% include "fellchensammlung/partials/rescue_orgs/partial-rescue-organization.html" %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@@ -6,21 +6,7 @@
|
|||||||
<h1 class="title is-1">{% translate 'Vermittlung deaktivieren' %}</h1>
|
<h1 class="title is-1">{% translate 'Vermittlung deaktivieren' %}</h1>
|
||||||
<form method="post" enctype="multipart/form-data">
|
<form method="post" enctype="multipart/form-data">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="field">
|
{{ form }}
|
||||||
<label class="label" for="reason_for_closing">{% translate 'Warum schließt du die Vermittlung?' %}</label>
|
|
||||||
<div class="control">
|
|
||||||
<div class="select">
|
|
||||||
<select id="reason_for_closing" name="reason_for_closing">
|
|
||||||
<option value="closed_successful_with_notfellchen">{% translate 'Vermittelt mit Hilfe von Notfellchen' %}</option>
|
|
||||||
<option value="closed_successful_without_notfellchen">{% translate 'Vermittelt ohne Hilfe von Notfellchen' %}</option>
|
|
||||||
<option value="closed_for_other_adoption_notice">{% translate 'Vermittlung zugunsten einer anderen geschlossen' %}</option>
|
|
||||||
<option value="closed_not_open_for_adoption_anymore">{% translate 'Nicht mehr zu vermitteln (z.B. aufgrund von Krankheit)' %}</option>
|
|
||||||
<option value="closed_animal_died">{% translate 'Tod des Tiers' %}</option>
|
|
||||||
<option value="closed_other">{% translate 'Anderer Grund' %}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input class="button is-warning" type="submit" value="{% translate "Vermittlung deaktivieren" %}">
|
<input class="button is-warning" type="submit" value="{% translate "Vermittlung deaktivieren" %}">
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
{% extends "fellchensammlung/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load widget_tweaks %}
|
||||||
|
{% load admin_urls %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
<title>Social Media Post für {{ adoption_notice }}</title>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="title is-1">Social Media Post für {{ adoption_notice }}</h1>
|
||||||
|
<div class="columns block">
|
||||||
|
<div class="column">
|
||||||
|
<div class="box">
|
||||||
|
{% include 'fellchensammlung/partials/social_media/post-to-fedi.html' %}
|
||||||
|
<p class="block">
|
||||||
|
{% blocktranslate %}
|
||||||
|
|
||||||
|
Die Vermittlung wird auf unserem Fediverse-Account gepostet
|
||||||
|
{% endblocktranslate %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<div class="box block">
|
||||||
|
<a href="{% url 'adoption-notice-story-pic' adoption_notice.pk %}"
|
||||||
|
class="button is-primary is-fullwidth">
|
||||||
|
<i class="fab fa-instagram fa-fw"></i> {% trans 'Instagram Storyvorlage' %}
|
||||||
|
</a>
|
||||||
|
<p class="block">
|
||||||
|
{% blocktranslate %}
|
||||||
|
Eine Vorlage für eine Instagram-Story im Format 1080x1980px.
|
||||||
|
{% endblocktranslate %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<div class="box block">
|
||||||
|
<a href="{% url 'adoption-notice-sharepic' adoption_notice.pk %}"
|
||||||
|
class="button is-primary block is-fullwidth">
|
||||||
|
<i class="fab fa-instagram fa-fw"></i> {% trans 'Instagram Post' %}
|
||||||
|
</a>
|
||||||
|
<p class="block">
|
||||||
|
{% blocktranslate %}
|
||||||
|
Eine Vorlage für eine Instagram-Post mit einer Slide pro Tier.
|
||||||
|
{% endblocktranslate %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="block">
|
||||||
|
<p>
|
||||||
|
{% blocktranslate %}
|
||||||
|
Die Vorlagen werden idealerweise im Grafikprogramm Inkscape weiterbearbeitet.
|
||||||
|
{% endblocktranslate %}
|
||||||
|
</p>
|
||||||
|
<a href="https://inkscape.org/" class="button is-link block is-fullwidth">
|
||||||
|
<i class="fab fa-inkscape fa-fw"></i>
|
||||||
|
{% translate 'Inkscape herunterladen' %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -20,39 +20,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="block">
|
<div class="block">
|
||||||
{% if action_was_posting %}
|
{% include "fellchensammlung/partials/social_media/post-to-fedi.html" %}
|
||||||
{% if posted_successfully %}
|
|
||||||
<div class="message is-success">
|
|
||||||
<div class="message-header">
|
|
||||||
{% translate 'Vermittlung gepostet' %}
|
|
||||||
</div>
|
|
||||||
<div class="message-body">
|
|
||||||
{% blocktranslate with post_url=post.url %}
|
|
||||||
Link zum Post: <a href={{ post_url }}>{{ post_url }}</a>
|
|
||||||
{% endblocktranslate %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div class="message is-danger">
|
|
||||||
<div class="message-header">
|
|
||||||
{% translate 'Vermittlung konnte nicht gepostet werden' %}
|
|
||||||
</div>
|
|
||||||
<div class="message-body">
|
|
||||||
{{ error_message }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<form class="cell" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" name="action" value="post_to_fedi">
|
|
||||||
<button class="button is-fullwidth is-warning is-primary" type="submit" id="submit">
|
|
||||||
<i class="fa-solid fa-bullhorn fa-fw"></i> {% translate "Vermittlung ins Fediverse posten" %}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
{% translate 'Adoptionsprozess' %}
|
{% translate 'Adoptionsprozess' %}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
{% if not adoption_notice.is_active %}
|
||||||
|
<div class="cover">{{ adoption_notice.status_description_short }}</div>
|
||||||
|
{% endif %}
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
{% include adoption_process_template %}
|
{% include adoption_process_template %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -31,5 +31,14 @@
|
|||||||
{{ adoption_notice.status_description }}
|
{{ adoption_notice.status_description }}
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
{% elif adoption_notice.is_disabled %}
|
||||||
|
<article class="message is-warning">
|
||||||
|
<div class="message-header">
|
||||||
|
<p>{% translate 'Vermittlung wurde gesperrt' %}</p>
|
||||||
|
</div>
|
||||||
|
<div class="message-body content">
|
||||||
|
{{ adoption_notice.status_description }}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load custom_tags %}
|
{% load custom_tags %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<a href="{{ adoption_notice.get_absolute_url }}" class="card-header">
|
<a href="{{ adoption_notice.get_absolute_url }}" class="card-header" target="_blank">
|
||||||
<div class="card-header-title">
|
<div class="card-header-title">
|
||||||
{{ adoption_notice.name }}
|
{{ adoption_notice.name }}
|
||||||
</div>
|
</div>
|
||||||
@@ -17,25 +17,21 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<div class="card-footer-item is-confirm">
|
<form class="card-footer-item is-confirm" method="post">
|
||||||
<form method="post">
|
{% csrf_token %}
|
||||||
{% csrf_token %}
|
<input type="hidden"
|
||||||
<input type="hidden"
|
name="adoption_notice_id"
|
||||||
name="adoption_notice_id"
|
value="{{ adoption_notice.pk }}">
|
||||||
value="{{ adoption_notice.pk }}">
|
<input type="hidden" name="action" value="checked_active">
|
||||||
<input type="hidden" name="action" value="checked_active">
|
<button style="width: 100%" type="submit">{% translate "Vermittlung noch aktuell" %}</button>
|
||||||
<button class="" type="submit">{% translate "Vermittlung noch aktuell" %}</button>
|
</form>
|
||||||
</form>
|
<form class="card-footer-item is-warning" method="post">
|
||||||
</div>
|
{% csrf_token %}
|
||||||
<div class="card-footer-item is-warning">
|
<input type="hidden"
|
||||||
<form method="post">
|
name="adoption_notice_id"
|
||||||
{% csrf_token %}
|
value="{{ adoption_notice.pk }}">
|
||||||
<input type="hidden"
|
<input type="hidden" name="action" value="checked_inactive">
|
||||||
name="adoption_notice_id"
|
<button style="width:100%" type="submit">{% translate "Vermittlung inaktiv" %}</button>
|
||||||
value="{{ adoption_notice.pk }}">
|
</form>
|
||||||
<input type="hidden" name="action" value="checked_inactive">
|
|
||||||
<button class="" type="submit">{% translate "Vermittlung inaktiv" %}</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
</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 }}">
|
value="{{ rescue_org.pk }}">
|
||||||
<input type="hidden" name="action" value="checked">
|
<input type="hidden" name="action" value="checked">
|
||||||
<button class="" type="submit">{% translate "Organisation geprüft" %}</button>
|
<button class="" type="submit">{% translate "Organisation geprüft" %}</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer-item is-danger">
|
<div class="card-footer-item is-warning">
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden"
|
<input type="hidden"
|
||||||
name="rescue_organization_id"
|
name="rescue_organization_id"
|
||||||
value="{{ rescue_org.pk }}">
|
value="{{ rescue_org.pk }}">
|
||||||
<input type="hidden" name="action" value="exclude">
|
<input type="hidden" name="action" value="toggle_active_communication">
|
||||||
<button class="" type="submit">{% translate "Von Check exkludieren" %}</button>
|
<button class="" type="submit">
|
||||||
|
{% if rescue_org.ongoing_communication %}
|
||||||
|
{% translate "Aktive Kommunikation beendet" %}
|
||||||
|
{% else %}
|
||||||
|
{% translate "Aktive Kommunikation" %}
|
||||||
|
{% endif %}
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card-footer-item is-danger">
|
||||||
|
<a href="{% url 'rescue-organization-exclude' rescue_organization_id=rescue_org.pk %}">{% translate "Von Check exkludieren" %}</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,19 +1,20 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<div class="grid is-col-min-2">
|
<div class="grid is-col-min-2">
|
||||||
{% if adoption_notice.num_per_sex.F > 0 %}
|
{% with num_per_sex=adoption_notice.num_per_sex %}
|
||||||
<span class="cell icon-text tag is-medium">
|
{% if num_per_sex.F > 0 %}
|
||||||
<span class="has-text-weight-bold is-size-4">{{ adoption_notice.num_per_sex.F }} </span>
|
<span class="cell icon-text tag is-medium">
|
||||||
|
<span class="has-text-weight-bold is-size-4">{{ num_per_sex.F }}</span>
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<img class="icon" src="{% static 'fellchensammlung/img/sexes/Female.png' %}"
|
<img class="icon" src="{% static 'fellchensammlung/img/sexes/Female.png' %}"
|
||||||
alt="{% translate 'weibliche Tiere' %}">
|
alt="{% translate 'weibliche Tiere' %}">
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% 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="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">
|
<span class="icon">
|
||||||
<img class="icon"
|
<img class="icon"
|
||||||
@@ -21,24 +22,25 @@
|
|||||||
alt="{% translate 'intersexuelle Tiere' %}">
|
alt="{% translate 'intersexuelle Tiere' %}">
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% 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="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">
|
<span class="icon">
|
||||||
<img class="icon" src="{% static 'fellchensammlung/img/sexes/Male.png' %}"
|
<img class="icon" src="{% static 'fellchensammlung/img/sexes/Male.png' %}"
|
||||||
alt="{% translate 'männliche Tiere' %}">
|
alt="{% translate 'männliche Tiere' %}">
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% 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="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">
|
<span class="icon">
|
||||||
<img class="icon"
|
<img class="icon"
|
||||||
src="{% static 'fellchensammlung/img/sexes/Male Neutered.png' %}"
|
src="{% static 'fellchensammlung/img/sexes/Male Neutered.png' %}"
|
||||||
alt="{% translate 'männlich, kastrierte Tiere' %}">
|
alt="{% translate 'männlich, kastrierte Tiere' %}">
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
</div>
|
</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">
|
<div class="grid is-col-min-15">
|
||||||
{% for rescue_org in rescue_orgs_to_check %}
|
{% for rescue_org in rescue_orgs_to_check %}
|
||||||
<div class="cell">
|
<div class="cell">
|
||||||
{% include "fellchensammlung/partials/partial-check-rescue-org.html" %}
|
{% include "fellchensammlung/partials/rescue_orgs/partial-check-rescue-org.html" %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
<div class="grid is-col-min-15">
|
<div class="grid is-col-min-15">
|
||||||
{% for rescue_org in rescue_orgs_with_ongoing_communication %}
|
{% for rescue_org in rescue_orgs_with_ongoing_communication %}
|
||||||
<div class="cell">
|
<div class="cell">
|
||||||
{% include "fellchensammlung/partials/partial-check-rescue-org.html" %}
|
{% include "fellchensammlung/partials/rescue_orgs/partial-check-rescue-org.html" %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
<div class="grid is-col-min-15">
|
<div class="grid is-col-min-15">
|
||||||
{% for rescue_org in rescue_orgs_last_checked %}
|
{% for rescue_org in rescue_orgs_last_checked %}
|
||||||
<div class="cell">
|
<div class="cell">
|
||||||
{% include "fellchensammlung/partials/partial-check-rescue-org.html" %}
|
{% include "fellchensammlung/partials/rescue_orgs/partial-check-rescue-org.html" %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if not subscribed_search %}
|
{% if not subscribed_search %}
|
||||||
<div class="block">{% translate 'Wenn du die Suche abbonierst, wirst du für neue Vermittlungen, die den Kriterien entsprechen, benachrichtigt.' %}</div>
|
<div class="block">{% translate 'Wenn du die Suche abonnierst, wirst du für neue Vermittlungen, die den Kriterien entsprechen, benachrichtigt.' %}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<button class="button is-primary is-fullwidth" type="submit" value="search" name="search">
|
<button class="button is-primary is-fullwidth" type="submit" value="search" name="search">
|
||||||
|
|||||||
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 django.utils.safestring import mark_safe
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
from fellchensammlung.tools.misc import time_since_as_hr_string
|
from fellchensammlung.tools.misc import time_since_as_hr_string
|
||||||
from notfellchen import settings
|
from notfellchen import settings
|
||||||
@@ -54,6 +55,11 @@ def get_oxitraffic_script_if_enabled():
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def api_base_url():
|
||||||
|
return reverse("api-base-url")
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
@stringfilter
|
@stringfilter
|
||||||
def pointdecimal(value):
|
def pointdecimal(value):
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import logging
|
import logging
|
||||||
import requests
|
import requests
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from fellchensammlung.models import SocialMediaPost, PlatformChoices
|
from fellchensammlung.models import SocialMediaPost, PlatformChoices, AdoptionNotice
|
||||||
from notfellchen import settings
|
from notfellchen import settings
|
||||||
|
|
||||||
|
|
||||||
@@ -86,7 +87,10 @@ class FediClient:
|
|||||||
|
|
||||||
|
|
||||||
def post_an_to_fedi(adoption_notice):
|
def post_an_to_fedi(adoption_notice):
|
||||||
client = FediClient(settings.fediverse_access_token, settings.fediverse_api_base_url)
|
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}
|
context = {"adoption_notice": adoption_notice}
|
||||||
status_text = render_to_string("fellchensammlung/misc/fediverse/an-post.md", context)
|
status_text = render_to_string("fellchensammlung/misc/fediverse/an-post.md", context)
|
||||||
@@ -101,3 +105,33 @@ def post_an_to_fedi(adoption_notice):
|
|||||||
platform=PlatformChoices.FEDIVERSE,
|
platform=PlatformChoices.FEDIVERSE,
|
||||||
url=response['url'], )
|
url=response['url'], )
|
||||||
return post
|
return post
|
||||||
|
|
||||||
|
|
||||||
|
def handle_post_fedi_action(adoption_notice: AdoptionNotice = SocialMediaPost.get_an_to_post()):
|
||||||
|
if adoption_notice is not None:
|
||||||
|
logging.info(f"Posting adoption notice: {adoption_notice} ({adoption_notice.id})")
|
||||||
|
try:
|
||||||
|
post = post_an_to_fedi(adoption_notice)
|
||||||
|
context = {"action_was_posting": True, "post": post, "posted_successfully": True}
|
||||||
|
except requests.exceptions.ConnectionError as e:
|
||||||
|
logging.error(f"Could not post fediverse post: {e}")
|
||||||
|
context = {"action_was_posting": True,
|
||||||
|
"posted_successfully": False,
|
||||||
|
"error_message": _("Verbindungsfehler. Vermittlung wurde nicht gepostet")}
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
logging.error(f"Could not post fediverse post: {e}")
|
||||||
|
context = {"action_was_posting": True,
|
||||||
|
"posted_successfully": False,
|
||||||
|
"error_message": _("Fehler beim Posten. Vermittlung wurde nicht gepostet. Das kann "
|
||||||
|
"z.B. an falschen Zugangsdaten liegen. Kontaktieren einen Admin.")}
|
||||||
|
except ConnectionError as e:
|
||||||
|
logging.error(f"Could not post fediverse post: {e}")
|
||||||
|
context = {"action_was_posting": True,
|
||||||
|
"posted_successfully": False,
|
||||||
|
"error_message": _(
|
||||||
|
"Fehler beim Posten, in der Konfiguration fehlen Zugangsdaten zu einem Fediverse Account")}
|
||||||
|
else:
|
||||||
|
context = {"action_was_posting": True,
|
||||||
|
"posted_successfully": False,
|
||||||
|
"error_message": _("Keine Vermittlung zum Posten gefunden.")}
|
||||||
|
return context
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from django.template.loader import render_to_string
|
|||||||
from fellchensammlung.models import AdoptionNotice
|
from fellchensammlung.models import AdoptionNotice
|
||||||
|
|
||||||
|
|
||||||
def export_svg(adoption_notice):
|
def export_svg(adoption_notice, template_name: str = "fellchensammlung/images/adoption-notice.svg"):
|
||||||
result = render_to_string(template_name="fellchensammlung/images/adoption-notice.svg",
|
result = render_to_string(template_name=template_name,
|
||||||
context={"adoption_notice": adoption_notice, })
|
context={"adoption_notice": adoption_notice, })
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -2,14 +2,19 @@ from datetime import timedelta
|
|||||||
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from fellchensammlung.models import User, AdoptionNotice, AdoptionNoticeStatusChoices, Animal, RescueOrganization
|
from fellchensammlung.models import User, AdoptionNotice, AdoptionNoticeStatusChoices, Animal, RescueOrganization, \
|
||||||
|
AllowUseOfMaterialsChices
|
||||||
|
|
||||||
|
|
||||||
def get_rescue_org_check_stats():
|
def get_rescue_org_check_stats():
|
||||||
timeframe = timezone.now().date() - timedelta(days=14)
|
timeframe = timezone.now().date() - timedelta(days=14)
|
||||||
num_rescue_orgs_to_check = RescueOrganization.objects.filter(exclude_from_check=False).filter(
|
num_rescue_orgs_to_check = RescueOrganization.objects.filter(exclude_from_check=False,
|
||||||
|
ongoing_communication=False).exclude(
|
||||||
|
allows_using_materials=AllowUseOfMaterialsChices.USE_MATERIALS_DENIED).filter(
|
||||||
last_checked__lt=timeframe).count()
|
last_checked__lt=timeframe).count()
|
||||||
num_rescue_orgs_checked = RescueOrganization.objects.filter(exclude_from_check=False).filter(
|
num_rescue_orgs_checked = RescueOrganization.objects.filter(exclude_from_check=False,
|
||||||
|
ongoing_communication=False).exclude(
|
||||||
|
allows_using_materials=AllowUseOfMaterialsChices.USE_MATERIALS_DENIED).filter(
|
||||||
last_checked__gte=timeframe).count()
|
last_checked__gte=timeframe).count()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import datetime as datetime
|
import datetime as datetime
|
||||||
import logging
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
from django.utils.translation import ngettext
|
from django.utils.translation import ngettext
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
@@ -75,3 +76,23 @@ def is_404(url):
|
|||||||
return result.status_code == 404
|
return result.status_code == 404
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
logging.warning(f"Request to {url} failed: {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")
|
UNCHECKED = "awaiting_action_unchecked", _("Unchecked")
|
||||||
|
|
||||||
class Closed(TextChoices):
|
class Closed(TextChoices):
|
||||||
SUCCESSFUL_WITH_NOTFELLCHEN = "closed_successful_with_notfellchen", _("Successful (with Notfellchen)")
|
SUCCESSFUL = "closed_successfully", _("Erfolgreich vermittelt")
|
||||||
SUCCESSFUL_WITHOUT_NOTFELLCHEN = "closed_successful_without_notfellchen", _("Successful (without Notfellchen)")
|
ANIMAL_DIED = "closed_animal_died", _("Tier gestorben")
|
||||||
ANIMAL_DIED = "closed_animal_died", _("Animal died")
|
FOR_OTHER_ADOPTION_NOTICE = ("closed_for_other_adoption_notice",
|
||||||
FOR_OTHER_ADOPTION_NOTICE = "closed_for_other_adoption_notice", _("Closed for other adoption notice")
|
_("Vermittlung wurde zugunsten einer anderen geschlossen."))
|
||||||
NOT_OPEN_ANYMORE = "closed_not_open_for_adoption_anymore", _("Not open for adoption anymore")
|
NOT_OPEN_ANYMORE = "closed_not_open_for_adoption_anymore", _("Tier(e) stehen nicht mehr zur Vermittlung bereit.")
|
||||||
LINK_TO_MORE_INFO_NOT_REACHABLE = "closed_link_to_more_info_not_reachable", _(
|
LINK_TO_MORE_INFO_NOT_REACHABLE = "closed_link_to_more_info_not_reachable", _(
|
||||||
"Der Link zu weiteren Informationen ist nicht mehr erreichbar.")
|
"Der Link zu weiteren Informationen ist nicht mehr erreichbar.")
|
||||||
OTHER = "closed_other", _("Other (closed)")
|
OTHER = "closed_other", _("Anderes")
|
||||||
|
|
||||||
class Disabled(TextChoices):
|
class Disabled(TextChoices):
|
||||||
AGAINST_RULES = "disabled_against_the_rules", _("Against the rules")
|
AGAINST_RULES = "disabled_against_the_rules", _("Against the rules")
|
||||||
@@ -97,8 +97,7 @@ class AdoptionNoticeStatusChoicesDescriptions:
|
|||||||
_ansc = AdoptionNoticeStatusChoices # Mapping for readability
|
_ansc = AdoptionNoticeStatusChoices # Mapping for readability
|
||||||
mapping = {_ansc.Active.SEARCHING.value: "",
|
mapping = {_ansc.Active.SEARCHING.value: "",
|
||||||
_ansc.Active.INTERESTED: _("Jemand hat bereits Interesse an den Tieren."),
|
_ansc.Active.INTERESTED: _("Jemand hat bereits Interesse an den Tieren."),
|
||||||
_ansc.Closed.SUCCESSFUL_WITH_NOTFELLCHEN: _("Vermittlung erfolgreich abgeschlossen."),
|
_ansc.Closed.SUCCESSFUL: _("Vermittlung erfolgreich abgeschlossen."),
|
||||||
_ansc.Closed.SUCCESSFUL_WITHOUT_NOTFELLCHEN: _("Vermittlung erfolgreich abgeschlossen."),
|
|
||||||
_ansc.Closed.ANIMAL_DIED: _("Die zu vermittelnden Tiere sind über die Regenbrücke gegangen."),
|
_ansc.Closed.ANIMAL_DIED: _("Die zu vermittelnden Tiere sind über die Regenbrücke gegangen."),
|
||||||
_ansc.Closed.FOR_OTHER_ADOPTION_NOTICE: _("Vermittlung wurde zugunsten einer anderen geschlossen."),
|
_ansc.Closed.FOR_OTHER_ADOPTION_NOTICE: _("Vermittlung wurde zugunsten einer anderen geschlossen."),
|
||||||
_ansc.Closed.NOT_OPEN_ANYMORE: _("Tier(e) stehen nicht mehr zur Vermittlung bereit."),
|
_ansc.Closed.NOT_OPEN_ANYMORE: _("Tier(e) stehen nicht mehr zur Vermittlung bereit."),
|
||||||
@@ -110,10 +109,11 @@ class AdoptionNoticeStatusChoicesDescriptions:
|
|||||||
_ansc.AwaitingAction.WAITING_FOR_REVIEW: _(
|
_ansc.AwaitingAction.WAITING_FOR_REVIEW: _(
|
||||||
"Deaktiviert bis Moderator*innen die Vermittlung prüfen können."),
|
"Deaktiviert bis Moderator*innen die Vermittlung prüfen können."),
|
||||||
_ansc.AwaitingAction.NEEDS_ADDITIONAL_INFO: _("Deaktiviert bis Informationen nachgetragen werden."),
|
_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.AGAINST_RULES: _("Die Vermittlung wurde gesperrt, da sie gegen die Regeln verstößt."),
|
||||||
_ansc.Disabled.OTHER: _("Vermittlung deaktiviert.")
|
_ansc.Disabled.OTHER: _("Vermittlung gesperrt.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -125,3 +125,22 @@ class AdoptionNoticeProcessTemplates:
|
|||||||
_bp = "fellchensammlung/partials/adoption_process/" # Base path for ease
|
_bp = "fellchensammlung/partials/adoption_process/" # Base path for ease
|
||||||
mapping = {AdoptionProcess.CONTACT_PERSON_IN_AN: f"{_bp}contact_person_in_an.html",
|
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:
|
class RescueOrgSearch:
|
||||||
def __init__(self, request):
|
def __init__(self, request):
|
||||||
|
self.name = None
|
||||||
self.area_search = None
|
self.area_search = None
|
||||||
self.max_distance = None
|
self.max_distance = None
|
||||||
self.location = None # Can either be Location (DjangoModel) or LocationProxy
|
self.location = None # Can either be Location (DjangoModel) or LocationProxy
|
||||||
@@ -229,6 +230,7 @@ class RescueOrgSearch:
|
|||||||
return fitting_rescue_orgs
|
return fitting_rescue_orgs
|
||||||
|
|
||||||
def rescue_org_search_from_request(self, request):
|
def rescue_org_search_from_request(self, request):
|
||||||
|
# Only search if request method is get with action search
|
||||||
if request.method == 'GET' and request.GET.get("action", False) == "search":
|
if request.method == 'GET' and request.GET.get("action", False) == "search":
|
||||||
self.search_form = RescueOrgSearchForm(request.GET)
|
self.search_form = RescueOrgSearchForm(request.GET)
|
||||||
self.search_form.is_valid()
|
self.search_form.is_valid()
|
||||||
|
|||||||
@@ -30,9 +30,15 @@ urlpatterns = [
|
|||||||
path("tier/<int:animal_id>/add-photo", views.add_photo_to_animal, name="animal-add-photo"),
|
path("tier/<int:animal_id>/add-photo", views.add_photo_to_animal, name="animal-add-photo"),
|
||||||
# ex: /adoption_notice/7/
|
# ex: /adoption_notice/7/
|
||||||
path("vermittlung/<int:adoption_notice_id>/", views.adoption_notice_detail, name="adoption-notice-detail"),
|
path("vermittlung/<int:adoption_notice_id>/", views.adoption_notice_detail, name="adoption-notice-detail"),
|
||||||
|
# ex: /adoption_notice/7/social-media-templates
|
||||||
|
path("vermittlung/<int:adoption_notice_id>/social-media-templates", views.adoption_notice_social_media_templates,
|
||||||
|
name="adoption-notice-social-media-template-selection"),
|
||||||
# ex: /adoption_notice/7/sharepic
|
# ex: /adoption_notice/7/sharepic
|
||||||
path("vermittlung/<int:adoption_notice_id>/sharepic", views.adoption_notice_sharepic,
|
path("vermittlung/<int:adoption_notice_id>/sharepic", views.adoption_notice_sharepic,
|
||||||
name="adoption-notice-sharepic"),
|
name="adoption-notice-sharepic"),
|
||||||
|
# ex: /adoption_notice/7/story
|
||||||
|
path("vermittlung/<int:adoption_notice_id>/storypic", views.adoption_notice_story_pic,
|
||||||
|
name="adoption-notice-story-pic"),
|
||||||
# ex: /adoption_notice/7/edit
|
# ex: /adoption_notice/7/edit
|
||||||
path("vermittlung/<int:adoption_notice_id>/edit", views.adoption_notice_edit, name="adoption-notice-edit"),
|
path("vermittlung/<int:adoption_notice_id>/edit", views.adoption_notice_edit, name="adoption-notice-edit"),
|
||||||
# ex: /vermittlung/5/add-photo
|
# ex: /vermittlung/5/add-photo
|
||||||
@@ -41,12 +47,19 @@ urlpatterns = [
|
|||||||
# ex: /adoption_notice/2/add-animal
|
# ex: /adoption_notice/2/add-animal
|
||||||
path("vermittlung/<int:adoption_notice_id>/add-animal", views.adoption_notice_add_animal,
|
path("vermittlung/<int:adoption_notice_id>/add-animal", views.adoption_notice_add_animal,
|
||||||
name="adoption-notice-add-animal"),
|
name="adoption-notice-add-animal"),
|
||||||
path("vermittlung/<int:adoption_notice_id>/close", views.deactivate_an,
|
path("vermittlung/<int:adoption_notice_id>/close", views.close_adoption_notice,
|
||||||
name="adoption-notice-close"),
|
name="adoption-notice-close"),
|
||||||
|
|
||||||
path("tierschutzorganisationen/", views.list_rescue_organizations, name="rescue-organizations"),
|
path("tierschutzorganisationen/", views.list_rescue_organizations, name="rescue-organizations"),
|
||||||
path("tierschutzorganisationen/<int:rescue_organization_id>/", views.detail_view_rescue_organization,
|
path("tierschutzorganisationen/<int:rescue_organization_id>/", views.detail_view_rescue_organization,
|
||||||
name="rescue-organization-detail"),
|
name="rescue-organization-detail"),
|
||||||
|
path("tierschutzorganisationen/add", views.rescue_org_create_or_update, name="rescue-organization-create"),
|
||||||
|
path("tierschutzorganisationen/<int:rescue_organization_id>/edit", views.rescue_org_create_or_update,
|
||||||
|
name="rescue-organization-edit"),
|
||||||
|
path("tierschutzorganisationen/<int:rescue_organization_id>/exkludieren", views.exclude_from_regular_check,
|
||||||
|
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,
|
path("tierschutzorganisationen/spezialisierung/<slug:species_slug>", views.specialized_rescues,
|
||||||
name="specialized-rescue-organizations"),
|
name="specialized-rescue-organizations"),
|
||||||
|
|
||||||
@@ -86,20 +99,12 @@ urlpatterns = [
|
|||||||
###########
|
###########
|
||||||
# ex: user/1
|
# ex: user/1
|
||||||
path("user/<int:user_id>/", views.user_by_id, name="user-detail"),
|
path("user/<int:user_id>/", views.user_by_id, name="user-detail"),
|
||||||
|
path("user/<int:user_id>/deactivate/", views.user_deactivate, name="user-deactivate"),
|
||||||
|
path("user/<int:user_id>/activate/", views.user_activate, name="user-activate"),
|
||||||
path("user/me/", views.my_profile, name="user-me"),
|
path("user/me/", views.my_profile, name="user-me"),
|
||||||
path("user/notifications/", views.my_notifications, name="user-notifications"),
|
path("user/notifications/", views.my_notifications, name="user-notifications"),
|
||||||
path('user/me/export/', views.export_own_profile, name='user-me-export'),
|
path('user/me/export/', views.export_own_profile, name='user-me-export'),
|
||||||
|
|
||||||
path('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"),
|
path('change-language', views.change_language, name="change-language"),
|
||||||
|
|
||||||
###########
|
###########
|
||||||
@@ -125,7 +130,7 @@ urlpatterns = [
|
|||||||
###################
|
###################
|
||||||
## External Site ##
|
## External Site ##
|
||||||
###################
|
###################
|
||||||
path('bulma/external-site/', views.external_site_warning, name="external-site"),
|
path('external-site/', views.external_site_warning, name="external-site"),
|
||||||
|
|
||||||
###############
|
###############
|
||||||
## TECHNICAL ##
|
## TECHNICAL ##
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
from django.contrib.auth.views import redirect_to_login
|
from django.contrib.auth.views import redirect_to_login
|
||||||
from django.core.paginator import Paginator
|
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.core.serializers import serialize
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
import json
|
import json
|
||||||
import requests
|
|
||||||
|
|
||||||
from .mail import notify_mods_new_report
|
from .mail import notify_mods_new_report
|
||||||
from notfellchen import settings
|
from notfellchen import settings
|
||||||
@@ -23,12 +21,14 @@ from fellchensammlung import logger
|
|||||||
from .models import AdoptionNotice, Text, Animal, Rule, Image, Report, ModerationAction, \
|
from .models import AdoptionNotice, Text, Animal, Rule, Image, Report, ModerationAction, \
|
||||||
User, Location, Subscriptions, Notification, RescueOrganization, \
|
User, Location, Subscriptions, Notification, RescueOrganization, \
|
||||||
Species, Log, Timestamp, TrustLevel, SexChoicesWithAll, SearchSubscription, \
|
Species, Log, Timestamp, TrustLevel, SexChoicesWithAll, SearchSubscription, \
|
||||||
ImportantLocation, SpeciesSpecificURL, NotificationTypeChoices, SocialMediaPost
|
ImportantLocation, SpeciesSpecificURL, NotificationTypeChoices, SocialMediaPost, AllowUseOfMaterialsChices
|
||||||
from .forms import AdoptionNoticeForm, ImageForm, ReportAdoptionNoticeForm, \
|
from .forms import AdoptionNoticeForm, ImageForm, ReportAdoptionNoticeForm, \
|
||||||
CommentForm, ReportCommentForm, AnimalForm, AdoptionNoticeFormAutoAnimal, SpeciesURLForm, RescueOrgInternalComment
|
CommentForm, ReportCommentForm, AnimalForm, AdoptionNoticeFormAutoAnimal, SpeciesURLForm, RescueOrgInternalComment, \
|
||||||
|
UpdateRescueOrgRegularCheckStatus, UserModCommentForm, CloseAdoptionNoticeForm, RescueOrgSearchByNameForm, \
|
||||||
|
RescueOrgForm
|
||||||
from .models import Language, Announcement
|
from .models import Language, Announcement
|
||||||
from .tools import i18n, img
|
from .tools import i18n, img
|
||||||
from .tools.fedi import post_an_to_fedi
|
from .tools.fedi import handle_post_fedi_action
|
||||||
from .tools.geo import GeoAPI, zoom_level_for_radius
|
from .tools.geo import GeoAPI, zoom_level_for_radius
|
||||||
from .tools.metrics import gather_metrics_data, get_rescue_org_check_stats
|
from .tools.metrics import gather_metrics_data, get_rescue_org_check_stats
|
||||||
from .tools.admin import clean_locations, get_unchecked_adoption_notices, deactivate_unchecked_adoption_notices, \
|
from .tools.admin import clean_locations, get_unchecked_adoption_notices, deactivate_unchecked_adoption_notices, \
|
||||||
@@ -36,7 +36,8 @@ from .tools.admin import clean_locations, get_unchecked_adoption_notices, deacti
|
|||||||
from .tasks import post_adoption_notice_save
|
from .tasks import post_adoption_notice_save
|
||||||
from rest_framework.authtoken.models import Token
|
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
|
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):
|
def adoption_notice_detail(request, adoption_notice_id):
|
||||||
adoption_notice = get_object_or_404(AdoptionNotice, id=adoption_notice_id)
|
adoption_notice = get_object_or_404(AdoptionNotice, id=adoption_notice_id)
|
||||||
|
if adoption_notice.is_disabled and not user_is_owner_or_trust_level(request.user, adoption_notice):
|
||||||
|
error_message = _("Die Vermittlung wurde versteckt und ist nur Admins zugänglich. Grund dafür kann z.b. ein "
|
||||||
|
"Regelverstoß sein.")
|
||||||
|
return render(request,
|
||||||
|
"fellchensammlung/errors/403.html",
|
||||||
|
context={"error_message": error_message},
|
||||||
|
status=403)
|
||||||
adoption_notice_meta = adoption_notice._meta
|
adoption_notice_meta = adoption_notice._meta
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
try:
|
try:
|
||||||
@@ -241,11 +249,17 @@ def search_important_locations(request, important_location_slug):
|
|||||||
|
|
||||||
|
|
||||||
def search(request, templatename="fellchensammlung/search.html"):
|
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
|
# 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. This will toggle the "subscribe" button
|
||||||
searched = False
|
searched = False
|
||||||
|
search_profile.add_status("Init Search")
|
||||||
search = AdoptionNoticeSearch()
|
search = AdoptionNoticeSearch()
|
||||||
|
search_profile.add_status("Search from request starting")
|
||||||
search.adoption_notice_search_from_request(request)
|
search.adoption_notice_search_from_request(request)
|
||||||
|
search_profile.add_status("Search from request finished")
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
searched = True
|
searched = True
|
||||||
if "subscribe_to_search" in request.POST:
|
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)
|
subscribed_search = search.get_subscription_or_none(request.user)
|
||||||
else:
|
else:
|
||||||
subscribed_search = None
|
subscribed_search = None
|
||||||
|
search_profile.add_status("End of POST")
|
||||||
site_title = _("Suche")
|
site_title = _("Suche")
|
||||||
site_description = _("Ratten in Tierheimen und Rattenhilfen in der Nähe suchen.")
|
site_description = _("Ratten in Tierheimen und Rattenhilfen in der Nähe suchen.")
|
||||||
canonical_url = reverse("search")
|
canonical_url = reverse("search")
|
||||||
|
|
||||||
|
search_profile.add_status("Start of context")
|
||||||
context = {"adoption_notices": search.get_adoption_notices(),
|
context = {"adoption_notices": search.get_adoption_notices(),
|
||||||
"search_form": search.search_form,
|
"search_form": search.search_form,
|
||||||
"place_not_found": search.place_not_found,
|
"place_not_found": search.place_not_found,
|
||||||
@@ -285,6 +301,10 @@ def search(request, templatename="fellchensammlung/search.html"):
|
|||||||
"site_title": site_title,
|
"site_title": site_title,
|
||||||
"site_description": site_description,
|
"site_description": site_description,
|
||||||
"canonical_url": canonical_url}
|
"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)
|
return render(request, templatename, context=context)
|
||||||
|
|
||||||
|
|
||||||
@@ -577,12 +597,23 @@ def report_detail_success(request, report_id):
|
|||||||
|
|
||||||
|
|
||||||
def user_detail(request, user, token=None):
|
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,
|
context = {"user": user,
|
||||||
"adoption_notices": AdoptionNotice.objects.filter(owner=user),
|
"adoption_notices": adoption_notices,
|
||||||
"notifications": Notification.objects.filter(user_to_notify=user, read=False),
|
"notifications": Notification.objects.filter(user_to_notify=user, read=False),
|
||||||
"search_subscriptions": SearchSubscription.objects.filter(owner=user), }
|
"search_subscriptions": SearchSubscription.objects.filter(owner=user), }
|
||||||
|
user_detail_profile.add_status("End of context")
|
||||||
if token is not None:
|
if token is not None:
|
||||||
context["token"] = token
|
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)
|
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(
|
context = {"notifications_unread": Notification.objects.filter(user_to_notify=request.user, read=False).order_by(
|
||||||
"-created_at"),
|
"-created_at"),
|
||||||
"notifications_read_last": Notification.objects.filter(user_to_notify=request.user,
|
"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)
|
return render(request, 'fellchensammlung/notifications.html', context=context)
|
||||||
|
|
||||||
|
|
||||||
|
def user_activate(request, user_id):
|
||||||
|
return user_de_activation(request, user_id, True)
|
||||||
|
|
||||||
|
|
||||||
|
def user_deactivate(request, user_id):
|
||||||
|
return user_de_activation(request, user_id, False)
|
||||||
|
|
||||||
|
|
||||||
|
def user_de_activation(request, user_id, is_to_be_active):
|
||||||
|
"""
|
||||||
|
Activates or deactivates a user
|
||||||
|
"""
|
||||||
|
user = User.objects.get(id=user_id)
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = UserModCommentForm(request.POST, instance=user)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
user_instance = form.save(commit=False)
|
||||||
|
user_instance.is_active = is_to_be_active
|
||||||
|
user_instance.save()
|
||||||
|
return redirect(reverse("user-detail", args=[user_instance.pk], ))
|
||||||
|
else:
|
||||||
|
form = UserModCommentForm(instance=user)
|
||||||
|
if is_to_be_active:
|
||||||
|
template = 'fellchensammlung/forms/form-activate-user.html'
|
||||||
|
else:
|
||||||
|
template = 'fellchensammlung/forms/form-deactivate-user.html'
|
||||||
|
return render(request, template, {'form': form})
|
||||||
|
|
||||||
|
|
||||||
@user_passes_test(user_is_trust_level_or_above)
|
@user_passes_test(user_is_trust_level_or_above)
|
||||||
def modqueue(request):
|
def modqueue(request):
|
||||||
open_reports = Report.objects.select_related("reportadoptionnotice", "reportcomment").filter(status=Report.WAITING)
|
open_reports = Report.objects.select_related("reportadoptionnotice", "reportcomment").filter(status=Report.WAITING)
|
||||||
@@ -790,6 +851,7 @@ def list_rescue_organizations(request, species=None, template='fellchensammlung/
|
|||||||
context = {"rescue_organizations_to_list": rescue_organizations_to_list,
|
context = {"rescue_organizations_to_list": rescue_organizations_to_list,
|
||||||
"show_rescue_orgs": True,
|
"show_rescue_orgs": True,
|
||||||
"elided_page_range": paginator.get_elided_page_range(page_number, on_each_side=2, on_ends=1),
|
"elided_page_range": paginator.get_elided_page_range(page_number, on_each_side=2, on_ends=1),
|
||||||
|
"org_name_search_form": RescueOrgSearchByNameForm(),
|
||||||
}
|
}
|
||||||
if org_search:
|
if org_search:
|
||||||
additional_context = {
|
additional_context = {
|
||||||
@@ -845,8 +907,10 @@ def rescue_organization_check(request, context=None):
|
|||||||
action = request.POST.get("action")
|
action = request.POST.get("action")
|
||||||
if action == "checked":
|
if action == "checked":
|
||||||
rescue_org.set_checked()
|
rescue_org.set_checked()
|
||||||
elif action == "exclude":
|
Log.objects.create(user=request.user, action="rescue_organization_checked", )
|
||||||
rescue_org.set_exclusion_from_checks()
|
elif action == "toggle_active_communication":
|
||||||
|
rescue_org.ongoing_communication = not rescue_org.ongoing_communication
|
||||||
|
rescue_org.save()
|
||||||
elif action == "set_species_url":
|
elif action == "set_species_url":
|
||||||
species_url_form = SpeciesURLForm(request.POST)
|
species_url_form = SpeciesURLForm(request.POST)
|
||||||
|
|
||||||
@@ -857,10 +921,13 @@ def rescue_organization_check(request, context=None):
|
|||||||
elif action == "update_internal_comment":
|
elif action == "update_internal_comment":
|
||||||
comment_form = RescueOrgInternalComment(request.POST, instance=rescue_org)
|
comment_form = RescueOrgInternalComment(request.POST, instance=rescue_org)
|
||||||
if comment_form.is_valid():
|
if comment_form.is_valid():
|
||||||
|
Log.objects.create(user=request.user, action="rescue_organization_added_internal_comment", )
|
||||||
comment_form.save()
|
comment_form.save()
|
||||||
|
|
||||||
rescue_orgs_to_check = RescueOrganization.objects.filter(exclude_from_check=False,
|
rescue_orgs_to_check = RescueOrganization.objects.filter(exclude_from_check=False,
|
||||||
ongoing_communication=False).order_by("last_checked")[:3]
|
ongoing_communication=False).exclude(
|
||||||
|
allows_using_materials=AllowUseOfMaterialsChices.USE_MATERIALS_DENIED).order_by(
|
||||||
|
"last_checked")[:3]
|
||||||
rescue_orgs_with_ongoing_communication = RescueOrganization.objects.filter(ongoing_communication=True).order_by(
|
rescue_orgs_with_ongoing_communication = RescueOrganization.objects.filter(ongoing_communication=True).order_by(
|
||||||
"updated_at")
|
"updated_at")
|
||||||
rescue_orgs_last_checked = RescueOrganization.objects.filter().order_by("-last_checked")[:10]
|
rescue_orgs_last_checked = RescueOrganization.objects.filter().order_by("-last_checked")[:10]
|
||||||
@@ -896,50 +963,116 @@ def rescue_organization_check_dq(request):
|
|||||||
return rescue_organization_check(request, context)
|
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)
|
@user_passes_test(user_is_trust_level_or_above)
|
||||||
def moderation_tools_overview(request):
|
def moderation_tools_overview(request):
|
||||||
context = None
|
context = None
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
action = request.POST.get("action")
|
action = request.POST.get("action")
|
||||||
if action == "post_to_fedi":
|
if action == "post_to_fedi":
|
||||||
adoption_notice = SocialMediaPost.get_an_to_post()
|
context = handle_post_fedi_action()
|
||||||
if adoption_notice is not None:
|
|
||||||
logging.info(f"Posting adoption notice: {adoption_notice} ({adoption_notice.id})")
|
|
||||||
try:
|
|
||||||
post = post_an_to_fedi(adoption_notice)
|
|
||||||
context = {"action_was_posting": True, "post": post, "posted_successfully": True}
|
|
||||||
except requests.exceptions.ConnectionError as e:
|
|
||||||
logging.error(f"Could not post fediverse post: {e}")
|
|
||||||
context = {"action_was_posting": True,
|
|
||||||
"posted_successfully": False,
|
|
||||||
"error_message": _("Verbindungsfehler. Vermittlung wurde nicht gepostet")}
|
|
||||||
except requests.exceptions.HTTPError as e:
|
|
||||||
logging.error(f"Could not post fediverse post: {e}")
|
|
||||||
context = {"action_was_posting": True,
|
|
||||||
"posted_successfully": False,
|
|
||||||
"error_message": _("Fehler beim Posten. Vermittlung wurde nicht gepostet. Das kann "
|
|
||||||
"z.B. an falschen Zugangsdaten liegen. Kontaktieren einen Admin.")}
|
|
||||||
else:
|
|
||||||
context = {"action_was_posting": True,
|
|
||||||
"posted_successfully": False,
|
|
||||||
"error_message": _("Keine Vermittlung zum Posten gefunden.")}
|
|
||||||
return render(request, 'fellchensammlung/mod-tool-overview.html', context=context)
|
return render(request, 'fellchensammlung/mod-tool-overview.html', context=context)
|
||||||
|
|
||||||
|
|
||||||
def deactivate_an(request, adoption_notice_id):
|
def close_adoption_notice(request, adoption_notice_id):
|
||||||
adoption_notice = get_object_or_404(AdoptionNotice, pk=adoption_notice_id)
|
adoption_notice = get_object_or_404(AdoptionNotice, pk=adoption_notice_id)
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
reason_for_closing = request.POST.get("reason_for_closing")
|
form = CloseAdoptionNoticeForm(request.POST, instance=adoption_notice)
|
||||||
if reason_for_closing not in AdoptionNoticeStatusChoices.Closed.values:
|
if form.is_valid():
|
||||||
return render(request, "fellchensammlung/errors/403.html", status=403)
|
form.save()
|
||||||
adoption_notice.adoption_notice_status = reason_for_closing
|
return redirect(reverse("adoption-notice-detail", args=[adoption_notice.pk], ))
|
||||||
adoption_notice.save()
|
else:
|
||||||
return redirect(reverse("adoption-notice-detail", args=[adoption_notice.pk], ))
|
form = CloseAdoptionNoticeForm(instance=adoption_notice)
|
||||||
context = {"adoption_notice": adoption_notice, }
|
context = {"adoption_notice": adoption_notice, "form": form}
|
||||||
|
|
||||||
return render(request, 'fellchensammlung/misc/deactivate-an.html', context=context)
|
return render(request, 'fellchensammlung/misc/deactivate-an.html', context=context)
|
||||||
|
|
||||||
|
|
||||||
def adoption_notice_sharepic(request, adoption_notice_id):
|
def adoption_notice_sharepic(request, adoption_notice_id):
|
||||||
adoption_notice = get_object_or_404(AdoptionNotice, pk=adoption_notice_id)
|
adoption_notice = get_object_or_404(AdoptionNotice, pk=adoption_notice_id)
|
||||||
svg_data = img.export_svg(adoption_notice)
|
svg_data = img.export_svg(adoption_notice)
|
||||||
return HttpResponse(svg_data, content_type="image/svg+xml")
|
return HttpResponse(svg_data, content_type="image/svg+xml",
|
||||||
|
headers={"Content-Disposition": f'attachment; filename="{adoption_notice.name}-post.svg"'},)
|
||||||
|
|
||||||
|
|
||||||
|
def adoption_notice_story_pic(request, adoption_notice_id):
|
||||||
|
adoption_notice = get_object_or_404(AdoptionNotice, pk=adoption_notice_id)
|
||||||
|
svg_data = img.export_svg(adoption_notice, "fellchensammlung/images/adoption-notice-story.svg")
|
||||||
|
return HttpResponse(svg_data, content_type="image/svg+xml",
|
||||||
|
headers={"Content-Disposition": f'attachment; filename="{adoption_notice.name}-story.svg"'})
|
||||||
|
|
||||||
|
|
||||||
|
def adoption_notice_social_media_templates(request, adoption_notice_id):
|
||||||
|
context = {}
|
||||||
|
if request.method == "POST":
|
||||||
|
action = request.POST.get("action")
|
||||||
|
if action == "post_to_fedi":
|
||||||
|
context = handle_post_fedi_action()
|
||||||
|
|
||||||
|
adoption_notice = get_object_or_404(AdoptionNotice, pk=adoption_notice_id)
|
||||||
|
context["adoption_notice"] = adoption_notice
|
||||||
|
return render(request, 'fellchensammlung/misc/social-media-template-selection.html', context=context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def rescue_org_create_or_update(request, rescue_organization_id=None):
|
||||||
|
"""
|
||||||
|
Create or update a rescue organization
|
||||||
|
"""
|
||||||
|
# Only users that are mods to create or edit it
|
||||||
|
if not user_is_trust_level_or_above(request.user, TrustLevel.MODERATOR):
|
||||||
|
return HttpResponseForbidden()
|
||||||
|
if rescue_organization_id:
|
||||||
|
rescue_org = get_object_or_404(RescueOrganization, pk=rescue_organization_id)
|
||||||
|
else:
|
||||||
|
rescue_org = None
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = RescueOrgForm(request.POST, instance=rescue_org)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
rescue_org = form.save()
|
||||||
|
|
||||||
|
"""Log"""
|
||||||
|
Log.objects.create(user=request.user, action="add_rescue_org",
|
||||||
|
text=f"{request.user} hat Tierschutzorganisation {rescue_org.pk} geändert")
|
||||||
|
return redirect(reverse("rescue-organization-detail", args=[rescue_org.pk], ))
|
||||||
|
else:
|
||||||
|
form = RescueOrgForm(instance=rescue_org)
|
||||||
|
return render(request, 'fellchensammlung/forms/form-rescue-organization.html',
|
||||||
|
context={"form": form, "rescue_org": rescue_org})
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ https://docs.djangoproject.com/en/3.2/topics/settings/
|
|||||||
For the full list of settings and their values, see
|
For the full list of settings and their values, see
|
||||||
https://docs.djangoproject.com/en/3.2/ref/settings/
|
https://docs.djangoproject.com/en/3.2/ref/settings/
|
||||||
"""
|
"""
|
||||||
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import os
|
import os
|
||||||
import configparser
|
import configparser
|
||||||
@@ -74,6 +74,12 @@ except configparser.NoSectionError:
|
|||||||
raise BaseException("No config found or no Django Secret is configured!")
|
raise BaseException("No config found or no Django Secret is configured!")
|
||||||
DEBUG = config.getboolean('django', 'debug', fallback=False)
|
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 """
|
""" DATABASE """
|
||||||
DB_BACKEND = config.get("database", "backend", fallback="sqlite3")
|
DB_BACKEND = config.get("database", "backend", fallback="sqlite3")
|
||||||
DB_NAME = config.get("database", "name", fallback="notfellchen.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_OPEN = True
|
||||||
REGISTRATION_SALT = "notfellchen"
|
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 """
|
""" SECURITY.TXT """
|
||||||
SEC_CONTACT = config.get("security", "Contact", fallback="julian-samuel@gebuehr.net")
|
SEC_CONTACT = config.get("security", "Contact", fallback="julian-samuel@gebuehr.net")
|
||||||
SEC_EXPIRES = config.get("security", "Expires", fallback="2028-03-17T07:00:00.000Z")
|
SEC_EXPIRES = config.get("security", "Expires", fallback="2028-03-17T07:00:00.000Z")
|
||||||
@@ -182,7 +219,11 @@ INSTALLED_APPS = [
|
|||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
|
"django.contrib.humanize",
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
|
'allauth',
|
||||||
|
'allauth.account',
|
||||||
|
'allauth.mfa',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
"django.contrib.sitemaps",
|
"django.contrib.sitemaps",
|
||||||
'fontawesomefree',
|
'fontawesomefree',
|
||||||
@@ -193,11 +234,15 @@ INSTALLED_APPS = [
|
|||||||
'rest_framework.authtoken',
|
'rest_framework.authtoken',
|
||||||
'drf_spectacular',
|
'drf_spectacular',
|
||||||
'drf_spectacular_sidecar', # required for Django collectstatic discovery
|
'drf_spectacular_sidecar', # required for Django collectstatic discovery
|
||||||
'widget_tweaks'
|
'widget_tweaks',
|
||||||
|
"debug_toolbar",
|
||||||
|
'admin_extra_buttons',
|
||||||
|
'simple_history',
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
"debug_toolbar.middleware.DebugToolbarMiddleware",
|
||||||
# Static file serving & caching
|
# Static file serving & caching
|
||||||
'whitenoise.middleware.WhiteNoiseMiddleware',
|
'whitenoise.middleware.WhiteNoiseMiddleware',
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
@@ -208,6 +253,9 @@ MIDDLEWARE = [
|
|||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
# allauth middleware, needs to be after message middleware
|
||||||
|
"allauth.account.middleware.AccountMiddleware",
|
||||||
|
'simple_history.middleware.HistoryRequestMiddleware',
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'notfellchen.urls'
|
ROOT_URLCONF = 'notfellchen.urls'
|
||||||
@@ -222,6 +270,7 @@ TEMPLATES = [
|
|||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
'context_processors': [
|
'context_processors': [
|
||||||
'django.template.context_processors.debug',
|
'django.template.context_processors.debug',
|
||||||
|
# Needed for allauth
|
||||||
'django.template.context_processors.request',
|
'django.template.context_processors.request',
|
||||||
'django.contrib.auth.context_processors.auth',
|
'django.contrib.auth.context_processors.auth',
|
||||||
'django.template.context_processors.media',
|
'django.template.context_processors.media',
|
||||||
|
|||||||
@@ -18,12 +18,14 @@ from django.conf.urls.i18n import i18n_patterns
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
|
|
||||||
|
from debug_toolbar.toolbar import debug_toolbar_urls
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
]
|
path('accounts/', include('allauth.urls')),
|
||||||
|
] + debug_toolbar_urls()
|
||||||
|
|
||||||
urlpatterns += i18n_patterns(
|
urlpatterns += i18n_patterns(
|
||||||
path("", include("fellchensammlung.urls")),
|
path("", include("fellchensammlung.urls")),
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
<div class="block">
|
<div class="block">
|
||||||
<a class="button is-warning" href="{% url 'password_reset' %}">{% translate "Passwort vergessen?" %}</a>
|
<a class="button is-warning" href="{% url 'password_reset' %}">{% translate "Passwort vergessen?" %}</a>
|
||||||
<a class="button is-link"
|
<a class="button is-link"
|
||||||
href="{% url 'django_registration_register' %}">{% translate "Registrieren" %}</a>
|
href="{% url 'account_signup' %}">{% translate "Registrieren" %}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||