Compare commits
81 Commits
a372be4af2
...
656a24ef02
| Author | SHA1 | Date | |
|---|---|---|---|
| 656a24ef02 | |||
| 74643db087 | |||
| 3a6fd3cee1 | |||
| 29e9d1bd8c | |||
| 3c5ca9ae00 | |||
| 3d1ad6112d | |||
| b843e67e9b | |||
| 4cab71e8fb | |||
| 969339a95f | |||
| e06efa1539 | |||
| 2fb6d2782f | |||
| f69eccd0e4 | |||
| e20e6d4b1d | |||
| 0352a60e28 | |||
| abeb14601a | |||
| f52225495d | |||
| 797b2c15f7 | |||
| e81618500b | |||
| f7a5da306c | |||
| 92a9b5c6c9 | |||
| 964aeb97a7 | |||
| 474e9eb0f8 | |||
| 7acc2c6eec | |||
| 5a02837d7f | |||
| 7ff0a9b489 | |||
| 9af4b58a4f | |||
| 7a20890f17 | |||
| f6c9e532f8 | |||
| f1698c4fd3 | |||
| cb82aeffde | |||
| e9c1ef2604 | |||
| d8f0f2b3be | |||
| 65f065f5ce | |||
| 5cba64e500 | |||
| 064784a222 | |||
| b890ef3563 | |||
| 962f2ae86c | |||
| c71a1940dd | |||
| 8b2913a8be | |||
| 111ffc2b2e | |||
| 600aa918ef | |||
| 68e13ed176 | |||
| 0fa4330f2c | |||
| c9289b1e8c | |||
| bb3136bfc7 | |||
| b708e9ecaf | |||
| f3619b2881 | |||
| 7572c92da5 | |||
| 5a2b11b44e | |||
| df15ea100b | |||
| 3da6e90f73 | |||
| f784ab0c78 | |||
| ebaa477cff | |||
| b4be21bf45 | |||
| 0df36df9d8 | |||
| a5754b2633 | |||
| 7c6e01a436 | |||
| ad90429ec7 | |||
| 0e36237890 | |||
| 3261f5a90a | |||
| 1551c1bdf2 | |||
| 996bd7af67 | |||
| 21bd34c94d | |||
| fb581c940b | |||
| b428f46213 | |||
| 38fe55dd86 | |||
| 0da6c425fd | |||
| 81962ab9e7 | |||
| 48dd0a6a19 | |||
| 661827a957 | |||
| 242de5f749 | |||
| bd7f940987 | |||
| 0634671c84 | |||
| 1fb5be0cf8 | |||
| 3f9e4265e5 | |||
| de21b8b5e5 | |||
| fd481fef2e | |||
| 70f077e393 | |||
| 1c7d943a21 | |||
| 41873ebfe5 | |||
| fc2dbde064 |
1
.gitignore
vendored
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
# Database
|
# Database
|
||||||
notfellchen
|
notfellchen
|
||||||
|
*.sq3
|
||||||
|
|
||||||
# Geojson from imports
|
# Geojson from imports
|
||||||
*.geojson
|
*.geojson
|
||||||
|
|||||||
74
docs/_ext/drawio.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from docutils import nodes
|
||||||
|
from sphinx.application import Sphinx
|
||||||
|
from sphinx.util.docutils import SphinxDirective
|
||||||
|
from sphinx.util.typing import ExtensionMetadata
|
||||||
|
|
||||||
|
|
||||||
|
class DrawioDirective(SphinxDirective):
|
||||||
|
"""A directive to show a drawio diagram!
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
.. drawio::
|
||||||
|
example-diagram.drawio.html
|
||||||
|
example-diagram.drawio.png
|
||||||
|
:alt: Example of a Draw.io diagram
|
||||||
|
"""
|
||||||
|
|
||||||
|
has_content = False
|
||||||
|
required_arguments = 2 # html and png
|
||||||
|
optional_arguments = 1
|
||||||
|
final_argument_whitespace = True # indicating if the final argument may contain whitespace
|
||||||
|
option_spec = {
|
||||||
|
"alt": str,
|
||||||
|
}
|
||||||
|
|
||||||
|
def run(self) -> list[nodes.Node]:
|
||||||
|
env = self.state.document.settings.env
|
||||||
|
builder = env.app.builder
|
||||||
|
|
||||||
|
# Resolve paths relative to the document
|
||||||
|
docdir = Path(env.doc2path(env.docname)).parent
|
||||||
|
html_rel = Path(self.arguments[0])
|
||||||
|
png_rel = Path(self.arguments[1])
|
||||||
|
html_path = (docdir / html_rel).resolve()
|
||||||
|
png_path = (docdir / png_rel).resolve()
|
||||||
|
|
||||||
|
alt_text = self.options.get("alt", "")
|
||||||
|
|
||||||
|
container = nodes.container()
|
||||||
|
|
||||||
|
# HTML output -> raw HTML node
|
||||||
|
if builder.format == "html":
|
||||||
|
# Embed the HTML file contents directly
|
||||||
|
try:
|
||||||
|
html_content = html_path.read_text(encoding="utf-8")
|
||||||
|
except OSError as e:
|
||||||
|
msg = self.state_machine.reporter.error(f"Cannot read HTML file: {e}")
|
||||||
|
return [msg]
|
||||||
|
aria_attribute = f' aria-label="{alt_text}"' if alt_text else ""
|
||||||
|
raw_html_node = nodes.raw(
|
||||||
|
"",
|
||||||
|
f'<div class="drawio-diagram"{aria_attribute}>{html_content}</div>',
|
||||||
|
format="html",
|
||||||
|
)
|
||||||
|
container += raw_html_node
|
||||||
|
else:
|
||||||
|
# Other outputs -> PNG image node
|
||||||
|
image_node = nodes.image(uri=png_path)
|
||||||
|
container += image_node
|
||||||
|
|
||||||
|
return [container]
|
||||||
|
|
||||||
|
|
||||||
|
def setup(app: Sphinx) -> ExtensionMetadata:
|
||||||
|
app.add_directive("drawio", DrawioDirective)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"version": "0.2",
|
||||||
|
"parallel_read_safe": True,
|
||||||
|
"parallel_write_safe": True,
|
||||||
|
}
|
||||||
12
docs/conf.py
@@ -16,6 +16,10 @@
|
|||||||
# import sys
|
# import sys
|
||||||
# sys.path.insert(0, os.path.abspath('.'))
|
# sys.path.insert(0, os.path.abspath('.'))
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
sys.path.append(str(Path('_ext').resolve()))
|
||||||
|
|
||||||
# -- Project information -----------------------------------------------------
|
# -- Project information -----------------------------------------------------
|
||||||
|
|
||||||
@@ -28,7 +32,6 @@ version = ''
|
|||||||
# The full version, including alpha/beta/rc tags
|
# The full version, including alpha/beta/rc tags
|
||||||
release = '0.2.0'
|
release = '0.2.0'
|
||||||
|
|
||||||
|
|
||||||
# -- General configuration ---------------------------------------------------
|
# -- General configuration ---------------------------------------------------
|
||||||
|
|
||||||
# If your documentation needs a minimal Sphinx version, state it here.
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
@@ -40,6 +43,7 @@ release = '0.2.0'
|
|||||||
# ones.
|
# ones.
|
||||||
extensions = [
|
extensions = [
|
||||||
'sphinx.ext.ifconfig',
|
'sphinx.ext.ifconfig',
|
||||||
|
'drawio'
|
||||||
]
|
]
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
@@ -69,7 +73,6 @@ exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
|||||||
# The name of the Pygments (syntax highlighting) style to use.
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
pygments_style = None
|
pygments_style = None
|
||||||
|
|
||||||
|
|
||||||
# -- Options for HTML output -------------------------------------------------
|
# -- Options for HTML output -------------------------------------------------
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
@@ -104,7 +107,6 @@ html_static_path = ['_static']
|
|||||||
# Output file base name for HTML help builder.
|
# Output file base name for HTML help builder.
|
||||||
htmlhelp_basename = 'notfellchen'
|
htmlhelp_basename = 'notfellchen'
|
||||||
|
|
||||||
|
|
||||||
# -- Options for LaTeX output ------------------------------------------------
|
# -- Options for LaTeX output ------------------------------------------------
|
||||||
|
|
||||||
latex_elements = {
|
latex_elements = {
|
||||||
@@ -133,7 +135,6 @@ latex_documents = [
|
|||||||
'Julian-Samuel Gebühr', 'manual'),
|
'Julian-Samuel Gebühr', 'manual'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# -- Options for manual page output ------------------------------------------
|
# -- Options for manual page output ------------------------------------------
|
||||||
|
|
||||||
# One entry per manual page. List of tuples
|
# One entry per manual page. List of tuples
|
||||||
@@ -143,7 +144,6 @@ man_pages = [
|
|||||||
[author], 1)
|
[author], 1)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# -- Options for Texinfo output ----------------------------------------------
|
# -- Options for Texinfo output ----------------------------------------------
|
||||||
|
|
||||||
# Grouping the document tree into Texinfo files. List of tuples
|
# Grouping the document tree into Texinfo files. List of tuples
|
||||||
@@ -155,7 +155,6 @@ texinfo_documents = [
|
|||||||
'Miscellaneous'),
|
'Miscellaneous'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# -- Options for Epub output -------------------------------------------------
|
# -- Options for Epub output -------------------------------------------------
|
||||||
|
|
||||||
# Bibliographic Dublin Core info.
|
# Bibliographic Dublin Core info.
|
||||||
@@ -173,5 +172,4 @@ epub_title = project
|
|||||||
# A list of files that should not be packed into the epub file.
|
# A list of files that should not be packed into the epub file.
|
||||||
epub_exclude_files = ['search.html']
|
epub_exclude_files = ['search.html']
|
||||||
|
|
||||||
|
|
||||||
# -- Extension configuration -------------------------------------------------
|
# -- Extension configuration -------------------------------------------------
|
||||||
|
|||||||
BIN
docs/user/Screenshot-Moderationstools.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
docs/user/Screenshot-hilfreiche-Links.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
11
docs/user/Tiere-in-Vermittlung-entdecken.drawio.html
Normal file
BIN
docs/user/Tiere-in-Vermittlung-entdecken.drawio.png
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
docs/user/Vermittlung-Lifecycle.drawio.png
Normal file
|
After Width: | Height: | Size: 150 KiB |
11
docs/user/Vermittlung_Lifecycle.drawio.html
Normal file
@@ -6,14 +6,27 @@ Jede Vermittlung kann abonniert werden. Dafür klickst du auf die Glocke neben d
|
|||||||
|
|
||||||
.. image:: abonnieren.png
|
.. image:: abonnieren.png
|
||||||
|
|
||||||
|
|
||||||
|
Einstellungen
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Du kannst E-Mail Benachrichtigungen in den Einstellungen deaktivieren.
|
||||||
|
|
||||||
|
.. image::
|
||||||
|
einstellungen-benachrichtigungen.png
|
||||||
|
:alt: Screenshot der Profileinstellungen in Notfellchen. Ein roter Pfeil zeigt auf einen Schalter "E-Mail Benachrichtigungen"
|
||||||
|
|
||||||
Auf der Website
|
Auf der Website
|
||||||
+++++++++++++++
|
+++++++++++++++
|
||||||
|
|
||||||
|
.. image::
|
||||||
|
screenshot-benachrichtigungen.png
|
||||||
|
:alt: Screenshot der Menüleiste von Notfellchen.org. Neben dem Symbol einer Glocke steht die Zahl 27.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
E-Mail
|
E-Mail
|
||||||
++++++
|
++++++
|
||||||
|
|
||||||
Mit während deiner :doc:`registrierung` gibst du eine E-Mail Addresse an.
|
Mit während deiner :doc:`registrierung` gibst du eine E-Mail Adresse an. An diese senden wir Benachrichtigungen, außer
|
||||||
|
du deaktiviert dies wie oben beschrieben.
|
||||||
Benachrichtigungen senden wir per Mail - du kannst das jederzeit in den Einstellungen deaktivieren.
|
|
||||||
BIN
docs/user/einstellungen-benachrichtigungen.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
58
docs/user/erste-schritte.rst
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
Erste Schritte
|
||||||
|
==============
|
||||||
|
|
||||||
|
Tiere zum Adoptieren suchen
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
Wenn du Tiere zum adoptieren suchst, brauchst du keinen Account. Du kannst bequem die `Suche <https://notfellchen.org/suchen/>`_ nutzen, um Tiere zur Adoption in deiner Nähe zu finden.
|
||||||
|
Wenn dich eine Vermittlung interessiert, kannst du folgendes tun
|
||||||
|
|
||||||
|
* die Vermittlung aufrufen um Details zu sehen
|
||||||
|
* den Link :guilabel:`Weitere Informationen` anklicken um auf der Tierheimwebsite mehr zu erfahren
|
||||||
|
* per Kommentar weitere Informationen erfragen oder hinzufügen
|
||||||
|
|
||||||
|
Wenn du die Tiere tatsächlich informieren willst, folge der Anleitung unter :guilabel:`Adoptionsprozess`.
|
||||||
|
Dieser kann sich je nach Tierschutzorganisation unterscheiden.
|
||||||
|
|
||||||
|
.. image::
|
||||||
|
screenshot-adoptionsprozess.png
|
||||||
|
:alt: Screenshot der Sektion "Adoptionsprozess" einer Vermittlungsanzeige. Der Prozess ist folgendermaßen: 1. Link zu "Weiteren Informationen" prüfen, 2. Organization kontaktieren, 3. Bei erfolgreicher Vermittlung: Vermittlung als geschlossen melden
|
||||||
|
|
||||||
|
Suchen abonnieren
|
||||||
|
+++++++++++++++++
|
||||||
|
|
||||||
|
Es kann sein, dass es in deiner Umgebung keine passenden Tiere für deine Suche gibt. Damit du nicht ständig wieder Suchen musst, gibt es die Funktion "Suche abonnieren".
|
||||||
|
Wenn du eine Suche abonnierst, wirst du für neue Vermittlungen, die den Kriterien der Suche entsprechen, benachrichtigt.
|
||||||
|
|
||||||
|
.. image::
|
||||||
|
screenshot-suche-abonnieren.png
|
||||||
|
:alt: Screenshot der Suchmaske auf Notfellchen.org . Ein roter Pfeil zeigt auf den Button "Suche abonnieren"
|
||||||
|
|
||||||
|
.. important::
|
||||||
|
|
||||||
|
Um Suchen zu abonnieren brauchst du einen Account. Wie du einen Account erstellst erfährst du hier: :doc:`registrierung`.
|
||||||
|
|
||||||
|
.. hint::
|
||||||
|
|
||||||
|
Mehr über Benachrichtigungen findest du hier: :doc:`benachrichtigungen`.
|
||||||
|
|
||||||
|
Vermittlungen hinzufügen
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
Gehe zu `Vermittlung hinzufügen <https://notfellchen.org/vermitteln/>`_ um eine neue Vermittlung einzustellen.
|
||||||
|
Füge alle Informationen die du hast hinzu.
|
||||||
|
|
||||||
|
.. important::
|
||||||
|
|
||||||
|
Um Vermittlungen hinzuzufügen brauchst du einen Account.
|
||||||
|
Wie du einen Account erstellst erfährst du hier: :doc:`registrierung`.
|
||||||
|
|
||||||
|
|
||||||
|
.. important::
|
||||||
|
|
||||||
|
Vermittlungen die du einstellst müssen erst durch Moderator\*innen freigeschaltet werden. Das passiert normalerweise
|
||||||
|
innerhalb von 24 Stunden. Wenn deine Vermittlung dann noch nicht freigeschaltet ist, prüfe bitte dein E-Mail Postfach,
|
||||||
|
es könnte sein, dass die Moderator\*innen Rückfragen haben. Melde dich gerne unter info@notfellchen.org, wenn deine
|
||||||
|
Vermittlung nach 24 Stunden nicht freigeschaltet ist.
|
||||||
|
|
||||||
|
|
||||||
@@ -1,11 +1,17 @@
|
|||||||
******************
|
****************
|
||||||
User Dokumentation
|
Benutzerhandbuch
|
||||||
******************
|
****************
|
||||||
|
|
||||||
|
Im Benutzerhandbuch findest du Informationen zur Benutzung von `notfellchen.org <https://notfellchen.org>`_.
|
||||||
|
Solltest du darüber hinaus Fragen haben, komm gerne auf uns zu: info@notfellchen.org
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
:caption: Inhalt:
|
:caption: Inhalt:
|
||||||
|
|
||||||
|
erste-schritte.rst
|
||||||
registrierung.rst
|
registrierung.rst
|
||||||
vermittlungen.rst
|
vermittlungen.rst
|
||||||
moderationskonzept.rst
|
moderationskonzept.rst
|
||||||
benachrichtigungen.rst
|
benachrichtigungen.rst
|
||||||
|
organisationen-pruefen.rst
|
||||||
|
|||||||
55
docs/user/organisationen-pruefen.rst
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
Tiere in Vermittlung systematisch entdecken & eintragen
|
||||||
|
=======================================================
|
||||||
|
|
||||||
|
Notfellchen hat eine Liste der meisten deutschen Tierheime und anderer Tierschutzorganisationen.
|
||||||
|
Die meisten dieser Organisationen nehmen Tiere auf die bei Notfellchen eingetragen werden können.
|
||||||
|
Es ist daher das Ziel, diese Organisationen alle zwei Wochen auf neue Tiere zu prüfen.
|
||||||
|
|
||||||
|
|
||||||
|
+-------------------------------------------------+---------+----------------------+
|
||||||
|
| Gruppe | Anzahl | Zuletzt aktualisiert |
|
||||||
|
+=================================================+=========+======================+
|
||||||
|
| Tierschutzorganisationen im Verzeichnis | 550 | Oktober 2025 |
|
||||||
|
+-------------------------------------------------+---------+----------------------+
|
||||||
|
| Tierschutzorganisationen in regelmäßigerPrüfung | 412 | Oktober 2025 |
|
||||||
|
+-------------------------------------------------+---------+----------------------+
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
Organisationen auf neue Tiere zu prüfen ist eine Funktion für Moderator\*innen. Falls du Lust hast mitzuhelfen,
|
||||||
|
meld dich unter info@notfellchen.org
|
||||||
|
|
||||||
|
Als Moderator\*in kannst du direkt auf den `Moderations-Check <https://notfellchen.org/organization-check/>`_ zugreifen
|
||||||
|
oder findest ihn in unter :menuselection:`Hilfreiche Links --> Moderationstools`:
|
||||||
|
|
||||||
|
.. image::
|
||||||
|
Screenshot-hilfreiche-Links.png
|
||||||
|
:alt: Screenshot der Hilfreichen Links. Zur Auswahl stehen "Tierheime in der Nähe","Moderationstools" und "Admin-Bereich"
|
||||||
|
|
||||||
|
.. image::
|
||||||
|
Screenshot-Moderationstools.png
|
||||||
|
:alt: Screenshot der Moderationstools. Zur Auswahl stehen "Moderationswarteschlange", "Up-to-Date Check", "Organisations-Check" und "Vermittlung ins Fediverse posten".
|
||||||
|
|
||||||
|
|
||||||
|
Arbeitsmodus
|
||||||
|
------------
|
||||||
|
|
||||||
|
.. drawio::
|
||||||
|
Tiere-in-Vermittlung-entdecken.drawio.html
|
||||||
|
Tiere-in-Vermittlung-entdecken.drawio.png
|
||||||
|
|
||||||
|
Shortcuts
|
||||||
|
---------
|
||||||
|
|
||||||
|
Um die Prüfung schneller zu gestalten, gibt es eine Reihe von Shortcuts die du nutzen kannst. Aus Gründen der
|
||||||
|
Übersichtlichkeit sind im Folgenden auch Shortcuts im Browser aufgeführt.
|
||||||
|
|
||||||
|
+------------------------------------------------------+---------------+
|
||||||
|
| Aktion | Shortcut |
|
||||||
|
+======================================================+===============+
|
||||||
|
| Website der ersten Tierschutzorganisation öffnen | :kbd:`O` |
|
||||||
|
+------------------------------------------------------+---------------+
|
||||||
|
| Tab schließen (Firefox/Chrome) | :kbd:`STRG+W` |
|
||||||
|
+------------------------------------------------------+---------------+
|
||||||
|
| Erste Tierschutzorganisationa als geprüft markieren | :kbd:`C` |
|
||||||
|
+------------------------------------------------------+---------------+
|
||||||
BIN
docs/user/screenshot-adoptionsprozess.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
docs/user/screenshot-benachrichtigungen.png
Normal file
|
After Width: | Height: | Size: 205 KiB |
BIN
docs/user/screenshot-suche-abonnieren.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
docs/user/screenshot-suche.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
@@ -1,7 +1,7 @@
|
|||||||
Vermittlungen
|
Vermittlungen
|
||||||
=============
|
=============
|
||||||
|
|
||||||
Vermittlungen können von allen Nutzer*innen mit Account erstellt werden. Vermittlungen normaler Nutzer*innen kommen dann in eine Warteschlange und werden vom Admin & Modertionsteam geprüft und sichtbar geschaltet.
|
Vermittlungen können von allen Nutzer\*innen mit Account erstellt werden. Vermittlungen normaler Nutzer*innen kommen dann in eine Warteschlange und werden vom Admin & Modertionsteam geprüft und sichtbar geschaltet.
|
||||||
Tierheime und Pflegestellen können auf Anfrage einen Koordinations-Status bekommen, wodurch sie Vermittlungsanzeigen erstellen können die direkt öffentlich sichtbar sind.
|
Tierheime und Pflegestellen können auf Anfrage einen Koordinations-Status bekommen, wodurch sie Vermittlungsanzeigen erstellen können die direkt öffentlich sichtbar sind.
|
||||||
|
|
||||||
Jede Vermittlung hat ein "Zuletzt-geprüft" Datum, das anzeigt, wann ein Mensch zuletzt überprüft hat, ob die Anzeige noch aktuell ist.
|
Jede Vermittlung hat ein "Zuletzt-geprüft" Datum, das anzeigt, wann ein Mensch zuletzt überprüft hat, ob die Anzeige noch aktuell ist.
|
||||||
@@ -15,3 +15,114 @@ Die Kommentarfunktion von Vermittlungen ermöglicht es angemeldeten Nutzer*innen
|
|||||||
Ersteller*innen von Vermittlungen werden über neue Kommentare per Mail benachrichtigt, ebenso alle die die Vermittlung abonniert haben.
|
Ersteller*innen von Vermittlungen werden über neue Kommentare per Mail benachrichtigt, ebenso alle die die Vermittlung abonniert haben.
|
||||||
|
|
||||||
Kommentare können, wie Vermittlungen, gemeldet werden.
|
Kommentare können, wie Vermittlungen, gemeldet werden.
|
||||||
|
|
||||||
|
.. drawio::
|
||||||
|
Vermittlung_Lifecycle.drawio.html
|
||||||
|
Vermittlung-Lifecycle.drawio.png
|
||||||
|
:alt: Diagramm das den Prozess der Vermittlungen zeigt.
|
||||||
|
|
||||||
|
|
||||||
|
Adoption Notice Status Choices
|
||||||
|
++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
|
Aktiv
|
||||||
|
-----
|
||||||
|
|
||||||
|
Aktive Vermittlungen die über die Suche auffindbar sind.
|
||||||
|
|
||||||
|
.. list-table::
|
||||||
|
:header-rows: 1
|
||||||
|
:width: 100%
|
||||||
|
:widths: 1 1 2
|
||||||
|
|
||||||
|
* - Value
|
||||||
|
- Label
|
||||||
|
- Description
|
||||||
|
|
||||||
|
* - ``active_searching``
|
||||||
|
- Searching
|
||||||
|
-
|
||||||
|
|
||||||
|
* - ``active_interested``
|
||||||
|
- Interested
|
||||||
|
- Jemand hat bereits Interesse an den Tieren.
|
||||||
|
|
||||||
|
Warte auf Aktion
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Vermittlungen in diesem Status warten darauf, dass ein Mensch sie überprüft. Sie können nicht über die Suche gefunden werden.
|
||||||
|
|
||||||
|
.. list-table::
|
||||||
|
:header-rows: 1
|
||||||
|
:width: 100%
|
||||||
|
:widths: 1 1 2
|
||||||
|
|
||||||
|
* - ``awaiting_action_waiting_for_review``
|
||||||
|
- Waiting for review
|
||||||
|
- Neue Vermittlung die deaktiviert ist bis Moderator*innen sie überprüfen.
|
||||||
|
|
||||||
|
* - ``awaiting_action_needs_additional_info``
|
||||||
|
- Needs additional info
|
||||||
|
- Deaktiviert bis Informationen nachgetragen werden.
|
||||||
|
|
||||||
|
* - ``disabled_unchecked``
|
||||||
|
- Unchecked
|
||||||
|
- Vermittlung deaktiviert bis sie vom Team auf Aktualität geprüft wurde.
|
||||||
|
|
||||||
|
Geschlossen
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Geschlossene Vermittlungen tauchen in keiner Suche auf. Sie werden aber weiterhin angezeigt, wenn der Link zu ihnen direkt aufgerufen wird.
|
||||||
|
|
||||||
|
.. list-table::
|
||||||
|
:header-rows: 1
|
||||||
|
:width: 100%
|
||||||
|
:widths: 1 1 2
|
||||||
|
|
||||||
|
* - ``closed_successful_with_notfellchen``
|
||||||
|
- Successful (with Notfellchen)
|
||||||
|
- Vermittlung erfolgreich abgeschlossen.
|
||||||
|
|
||||||
|
* - ``closed_successful_without_notfellchen``
|
||||||
|
- Successful (without Notfellchen)
|
||||||
|
- Vermittlung erfolgreich abgeschlossen.
|
||||||
|
|
||||||
|
* - ``closed_animal_died``
|
||||||
|
- Animal died
|
||||||
|
- Die zu vermittelnden Tiere sind über die Regenbrücke gegangen.
|
||||||
|
|
||||||
|
* - ``closed_for_other_adoption_notice``
|
||||||
|
- Closed for other adoption notice
|
||||||
|
- Vermittlung wurde zugunsten einer anderen geschlossen.
|
||||||
|
|
||||||
|
* - ``closed_not_open_for_adoption_anymore``
|
||||||
|
- Not open for adoption anymore
|
||||||
|
- Tier(e) stehen nicht mehr zur Vermittlung bereit.
|
||||||
|
|
||||||
|
* - ``closed_link_to_more_info_not_reachable``
|
||||||
|
- Der Link zu weiteren Informationen ist nicht mehr erreichbar.
|
||||||
|
- Der Link zu weiteren Informationen ist nicht mehr erreichbar, die Vermittlung wurde daher automatisch deaktiviert.
|
||||||
|
|
||||||
|
* - ``closed_other``
|
||||||
|
- Other (closed)
|
||||||
|
- Vermittlung geschlossen.
|
||||||
|
|
||||||
|
Deaktiviert
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Deaktivierte Vermittlungen werden nur noch Moderator\*innen und Administrator\*innen angezeigt.
|
||||||
|
|
||||||
|
.. list-table::
|
||||||
|
:header-rows: 1
|
||||||
|
:width: 100%
|
||||||
|
:widths: 1 1 2
|
||||||
|
|
||||||
|
* - ``disabled_against_the_rules``
|
||||||
|
- Against the rules
|
||||||
|
- Vermittlung deaktiviert da sie gegen die Regeln verstößt.
|
||||||
|
|
||||||
|
* - ``disabled_other``
|
||||||
|
- Other (disabled)
|
||||||
|
- Vermittlung deaktiviert.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -28,3 +28,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,8 @@ 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]"
|
||||||
]
|
]
|
||||||
|
|
||||||
dynamic = ["version", "readme"]
|
dynamic = ["version", "readme"]
|
||||||
|
|||||||
@@ -205,6 +205,8 @@ def main():
|
|||||||
h = {'Authorization': f'Token {api_token}', "content-type": "application/json"}
|
h = {'Authorization': f'Token {api_token}', "content-type": "application/json"}
|
||||||
|
|
||||||
tierheime = overpass_result["features"]
|
tierheime = overpass_result["features"]
|
||||||
|
stats = {"num_updated_orgs": 0,
|
||||||
|
"num_inserted_orgs": 0}
|
||||||
|
|
||||||
for idx, tierheim in enumerate(tqdm(tierheime)):
|
for idx, tierheim in enumerate(tqdm(tierheime)):
|
||||||
# Check if data is low quality
|
# Check if data is low quality
|
||||||
@@ -229,11 +231,13 @@ def main():
|
|||||||
optional_data = ["email", "phone_number", "website", "description", "fediverse_profile", "facebook",
|
optional_data = ["email", "phone_number", "website", "description", "fediverse_profile", "facebook",
|
||||||
"instagram"]
|
"instagram"]
|
||||||
|
|
||||||
# Check if rescue organization exits
|
# Check if rescue organization exists
|
||||||
search_data = {"external_source_identifier": "OSM",
|
search_data = {"external_source_identifier": "OSM",
|
||||||
"external_object_identifier": f"{tierheim["id"]}"}
|
"external_object_identifier": f"{tierheim["id"]}"}
|
||||||
search_result = requests.get(f"{instance}/api/organizations", params=search_data, headers=h)
|
search_result = requests.get(f"{instance}/api/organizations", params=search_data, headers=h)
|
||||||
|
# Rescue organization exits
|
||||||
if search_result.status_code == 200:
|
if search_result.status_code == 200:
|
||||||
|
stats["num_updated_orgs"] += 1
|
||||||
org_id = search_result.json()[0]["id"]
|
org_id = search_result.json()[0]["id"]
|
||||||
logging.debug(f"{th_data.name} already exists as ID {org_id}.")
|
logging.debug(f"{th_data.name} already exists as ID {org_id}.")
|
||||||
org_patch_data = {"id": org_id,
|
org_patch_data = {"id": org_id,
|
||||||
@@ -248,7 +252,9 @@ def main():
|
|||||||
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
|
||||||
else:
|
else:
|
||||||
|
stats["num_inserted_orgs"] += 1
|
||||||
location = create_location(tierheim, instance, h)
|
location = create_location(tierheim, instance, h)
|
||||||
org_data = {"name": tierheim["properties"]["name"],
|
org_data = {"name": tierheim["properties"]["name"],
|
||||||
"external_object_identifier": f"{tierheim["id"]}",
|
"external_object_identifier": f"{tierheim["id"]}",
|
||||||
@@ -262,6 +268,7 @@ 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.")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -7,30 +7,26 @@ 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 .models import User, Language, Text, ReportComment, ReportAdoptionNotice, Log, Timestamp, SearchSubscription, \
|
from .models import Language, Text, ReportComment, ReportAdoptionNotice, Log, Timestamp, SearchSubscription, \
|
||||||
SpeciesSpecificURL, ImportantLocation, SocialMediaPost
|
SpeciesSpecificURL, ImportantLocation, SocialMediaPost
|
||||||
|
|
||||||
from .models import Animal, Species, RescueOrganization, AdoptionNotice, Location, Rule, Image, ModerationAction, \
|
from .models import Animal, Species, RescueOrganization, AdoptionNotice, Location, Rule, Image, ModerationAction, \
|
||||||
Comment, Report, Announcement, AdoptionNoticeStatus, User, Subscriptions, Notification
|
Comment, Announcement, User, Subscriptions, Notification
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from .tools.model_helpers import AdoptionNoticeStatusChoices
|
||||||
class StatusInline(admin.StackedInline):
|
|
||||||
model = AdoptionNoticeStatus
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(AdoptionNotice)
|
@admin.register(AdoptionNotice)
|
||||||
class AdoptionNoticeAdmin(admin.ModelAdmin):
|
class AdoptionNoticeAdmin(admin.ModelAdmin):
|
||||||
search_fields = ("name__icontains", "description__icontains")
|
search_fields = ("name__icontains", "description__icontains")
|
||||||
list_filter = ("owner",)
|
list_filter = ("owner",)
|
||||||
inlines = [
|
|
||||||
StatusInline,
|
|
||||||
]
|
|
||||||
actions = ("activate",)
|
actions = ("activate",)
|
||||||
|
|
||||||
def activate(self, request, queryset):
|
def activate(self, request, queryset):
|
||||||
for obj in queryset:
|
for obj in queryset:
|
||||||
obj.set_active()
|
obj.adoption_notice_status = AdoptionNoticeStatusChoices.Active.SEARCHING
|
||||||
|
obj.save()
|
||||||
|
|
||||||
activate.short_description = _("Ausgewählte Vermittlungen aktivieren")
|
activate.short_description = _("Ausgewählte Vermittlungen aktivieren")
|
||||||
|
|
||||||
@@ -162,10 +158,12 @@ class LocationAdmin(admin.ModelAdmin):
|
|||||||
ImportantLocationInline,
|
ImportantLocationInline,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@admin.register(SocialMediaPost)
|
@admin.register(SocialMediaPost)
|
||||||
class SocialMediaPostAdmin(admin.ModelAdmin):
|
class SocialMediaPostAdmin(admin.ModelAdmin):
|
||||||
list_filter = ("platform",)
|
list_filter = ("platform",)
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Animal)
|
admin.site.register(Animal)
|
||||||
admin.site.register(Species)
|
admin.site.register(Species)
|
||||||
admin.site.register(Rule)
|
admin.site.register(Rule)
|
||||||
@@ -173,7 +171,6 @@ admin.site.register(Image)
|
|||||||
admin.site.register(ModerationAction)
|
admin.site.register(ModerationAction)
|
||||||
admin.site.register(Language)
|
admin.site.register(Language)
|
||||||
admin.site.register(Announcement)
|
admin.site.register(Announcement)
|
||||||
admin.site.register(AdoptionNoticeStatus)
|
|
||||||
admin.site.register(Subscriptions)
|
admin.site.register(Subscriptions)
|
||||||
admin.site.register(Log)
|
admin.site.register(Log)
|
||||||
admin.site.register(Timestamp)
|
admin.site.register(Timestamp)
|
||||||
|
|||||||
@@ -3,6 +3,21 @@ from rest_framework import serializers
|
|||||||
import math
|
import math
|
||||||
|
|
||||||
|
|
||||||
|
class ImageSerializer(serializers.ModelSerializer):
|
||||||
|
width = serializers.SerializerMethodField()
|
||||||
|
height = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Image
|
||||||
|
fields = ['id', 'image', 'alt_text', 'width', 'height']
|
||||||
|
|
||||||
|
def get_width(self, obj):
|
||||||
|
return obj.image.width
|
||||||
|
|
||||||
|
def get_height(self, obj):
|
||||||
|
return obj.image.height
|
||||||
|
|
||||||
|
|
||||||
class AdoptionNoticeSerializer(serializers.HyperlinkedModelSerializer):
|
class AdoptionNoticeSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
location = serializers.PrimaryKeyRelatedField(
|
location = serializers.PrimaryKeyRelatedField(
|
||||||
queryset=Location.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
@@ -20,17 +35,18 @@ class AdoptionNoticeSerializer(serializers.HyperlinkedModelSerializer):
|
|||||||
required=False,
|
required=False,
|
||||||
allow_null=True
|
allow_null=True
|
||||||
)
|
)
|
||||||
|
url = serializers.SerializerMethodField()
|
||||||
|
|
||||||
photos = serializers.PrimaryKeyRelatedField(
|
photos = ImageSerializer(many=True, read_only=True)
|
||||||
queryset=Image.objects.all(),
|
|
||||||
many=True,
|
def get_url(self, obj):
|
||||||
required=False
|
return obj.get_full_url()
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AdoptionNotice
|
model = AdoptionNotice
|
||||||
fields = ['created_at', 'last_checked', "searching_since", "name", "description", "further_information",
|
fields = ['created_at', 'last_checked', "searching_since", "name", "description", "further_information",
|
||||||
"group_only", "location", "location_details", "organization", "photos"]
|
"group_only", "location", "location_details", "organization", "photos", "adoption_notice_status",
|
||||||
|
"url"]
|
||||||
|
|
||||||
|
|
||||||
class AdoptionNoticeGeoJSONSerializer(serializers.ModelSerializer):
|
class AdoptionNoticeGeoJSONSerializer(serializers.ModelSerializer):
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ 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
|
AdoptionNoticeGeoJSONView, RescueOrgGeoJSONView, AdoptionNoticePerOrgApiView
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@@ -14,6 +14,7 @@ urlpatterns = [
|
|||||||
path("organizations/", RescueOrganizationApiView.as_view(), name="api-organization-list"),
|
path("organizations/", RescueOrganizationApiView.as_view(), name="api-organization-list"),
|
||||||
path("organizations.geojson", RescueOrgGeoJSONView.as_view(), name="api-organization-list-geojson"),
|
path("organizations.geojson", RescueOrgGeoJSONView.as_view(), name="api-organization-list-geojson"),
|
||||||
path("organizations/<int:id>/", RescueOrganizationApiView.as_view(), name="api-organization-detail"),
|
path("organizations/<int:id>/", RescueOrganizationApiView.as_view(), name="api-organization-detail"),
|
||||||
|
path("organizations/<int:id>/adoption-notices", AdoptionNoticePerOrgApiView.as_view(), name="api-organization-adoption-notices"),
|
||||||
path("images/", AddImageApiView.as_view(), name="api-add-image"),
|
path("images/", AddImageApiView.as_view(), name="api-add-image"),
|
||||||
path("species/", SpeciesApiView.as_view(), name="api-species-list"),
|
path("species/", SpeciesApiView.as_view(), name="api-species-list"),
|
||||||
path("locations/", LocationApiView.as_view(), name="api-locations-list"),
|
path("locations/", LocationApiView.as_view(), name="api-locations-list"),
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from drf_spectacular.types import OpenApiTypes
|
||||||
from rest_framework.generics import ListAPIView
|
from rest_framework.generics import ListAPIView
|
||||||
|
|
||||||
from fellchensammlung.api.serializers import LocationSerializer, AdoptionNoticeGeoJSONSerializer
|
from fellchensammlung.api.serializers import LocationSerializer, AdoptionNoticeGeoJSONSerializer
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from fellchensammlung.models import AdoptionNotice, Animal, Log, TrustLevel, Location, AdoptionNoticeStatus
|
from fellchensammlung.models import Log, TrustLevel, Location, AdoptionNoticeStatusChoices
|
||||||
from fellchensammlung.tasks import post_adoption_notice_save, post_rescue_org_save
|
from fellchensammlung.tasks import post_adoption_notice_save, post_rescue_org_save
|
||||||
from rest_framework import status, serializers
|
from rest_framework import status, serializers
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
@@ -20,7 +21,7 @@ from .serializers import (
|
|||||||
SpeciesSerializer, RescueOrganizationSerializer,
|
SpeciesSerializer, RescueOrganizationSerializer,
|
||||||
)
|
)
|
||||||
from fellchensammlung.models import Animal, RescueOrganization, AdoptionNotice, Species, Image
|
from fellchensammlung.models import Animal, RescueOrganization, AdoptionNotice, Species, Image
|
||||||
from drf_spectacular.utils import extend_schema, inline_serializer
|
from drf_spectacular.utils import extend_schema, inline_serializer, OpenApiParameter
|
||||||
|
|
||||||
|
|
||||||
class AdoptionNoticeApiView(APIView):
|
class AdoptionNoticeApiView(APIView):
|
||||||
@@ -73,9 +74,9 @@ class AdoptionNoticeApiView(APIView):
|
|||||||
|
|
||||||
# Only set active when user has trust level moderator or higher
|
# Only set active when user has trust level moderator or higher
|
||||||
if request.user_to_notify.trust_level >= TrustLevel.MODERATOR:
|
if request.user_to_notify.trust_level >= TrustLevel.MODERATOR:
|
||||||
adoption_notice.set_active()
|
adoption_notice.adoption_notice_status = AdoptionNoticeStatusChoices.Active.SEARCHING
|
||||||
else:
|
else:
|
||||||
adoption_notice.set_unchecked()
|
adoption_notice.adoption_notice_status = AdoptionNoticeStatusChoices.AwaitingAction.WAITING_FOR_REVIEW
|
||||||
|
|
||||||
# Log the action
|
# Log the action
|
||||||
Log.objects.create(
|
Log.objects.create(
|
||||||
@@ -374,7 +375,7 @@ class LocationApiView(APIView):
|
|||||||
|
|
||||||
class AdoptionNoticeGeoJSONView(ListAPIView):
|
class AdoptionNoticeGeoJSONView(ListAPIView):
|
||||||
queryset = AdoptionNotice.objects.select_related('location').filter(location__isnull=False).filter(
|
queryset = AdoptionNotice.objects.select_related('location').filter(location__isnull=False).filter(
|
||||||
adoptionnoticestatus__major_status=AdoptionNoticeStatus.ACTIVE)
|
adoption_notice_status__in=AdoptionNoticeStatusChoices.Active.values)
|
||||||
serializer_class = AdoptionNoticeGeoJSONSerializer
|
serializer_class = AdoptionNoticeGeoJSONSerializer
|
||||||
renderer_classes = [GeoJSONRenderer]
|
renderer_classes = [GeoJSONRenderer]
|
||||||
|
|
||||||
@@ -383,3 +384,69 @@ class RescueOrgGeoJSONView(ListAPIView):
|
|||||||
queryset = RescueOrganization.objects.select_related('location').filter(location__isnull=False)
|
queryset = RescueOrganization.objects.select_related('location').filter(location__isnull=False)
|
||||||
serializer_class = RescueOrgeGeoJSONSerializer
|
serializer_class = RescueOrgeGeoJSONSerializer
|
||||||
renderer_classes = [GeoJSONRenderer]
|
renderer_classes = [GeoJSONRenderer]
|
||||||
|
|
||||||
|
|
||||||
|
class AdoptionNoticePerOrgApiView(APIView):
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
parameters=[
|
||||||
|
OpenApiParameter(
|
||||||
|
name='id',
|
||||||
|
required=False,
|
||||||
|
description='ID of the rescue organization from which to retrieve adoption notices.',
|
||||||
|
type=OpenApiTypes.INT
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name='in_hierarchy',
|
||||||
|
type=OpenApiTypes.BOOL,
|
||||||
|
required=False,
|
||||||
|
description='Show all Adoption Notices in hierarchy.',
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name='status',
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
required=False,
|
||||||
|
description='Show all Adoption Notices in a certain status. Comma separated list of values e.g. '
|
||||||
|
'"active,closed"',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
responses={200: AdoptionNoticeSerializer(many=True)}
|
||||||
|
)
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Retrieve adoption notices with their related animals and images.
|
||||||
|
"""
|
||||||
|
org_id = kwargs.get("id")
|
||||||
|
in_hierarchy = request.query_params.get("in_hierarchy")
|
||||||
|
an_status = request.query_params.get("status")
|
||||||
|
try:
|
||||||
|
org = RescueOrganization.objects.get(id=org_id)
|
||||||
|
except RescueOrganization.DoesNotExist:
|
||||||
|
return Response({"error": "Rescue Organization notice not found."}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
if in_hierarchy:
|
||||||
|
adoption_notices = org.adoption_notices_in_hierarchy
|
||||||
|
else:
|
||||||
|
adoption_notices = AdoptionNotice.objects.filter(organization=org)
|
||||||
|
if an_status:
|
||||||
|
status_list = an_status.lower().strip().split(",")
|
||||||
|
temporary_an_storage = []
|
||||||
|
if "active" in status_list:
|
||||||
|
active_ans = [adoption_notice for adoption_notice in adoption_notices if
|
||||||
|
adoption_notice.adoption_notice_status in AdoptionNoticeStatusChoices.Active.values]
|
||||||
|
temporary_an_storage.extend(active_ans)
|
||||||
|
if "closed" in status_list:
|
||||||
|
closed_ans = [adoption_notice for adoption_notice in adoption_notices if
|
||||||
|
adoption_notice.adoption_notice_status in AdoptionNoticeStatusChoices.Closed.values]
|
||||||
|
temporary_an_storage.extend(closed_ans)
|
||||||
|
if "disabled" in status_list:
|
||||||
|
disabled_ans = [adoption_notice for adoption_notice in adoption_notices if
|
||||||
|
adoption_notice.adoption_notice_status in AdoptionNoticeStatusChoices.Disabled.values]
|
||||||
|
temporary_an_storage.extend(disabled_ans)
|
||||||
|
if "awaiting_action" in status_list:
|
||||||
|
awaiting_action_ans = [adoption_notice for adoption_notice in adoption_notices if
|
||||||
|
adoption_notice.adoption_notice_status in AdoptionNoticeStatusChoices.AwaitingAction.values]
|
||||||
|
temporary_an_storage.extend(awaiting_action_ans)
|
||||||
|
adoption_notices = temporary_an_storage
|
||||||
|
serializer = AdoptionNoticeSerializer(adoption_notices, many=True, context={"request": request})
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|||||||
0
src/fellchensammlung/aviews/__init__.py
Normal file
37
src/fellchensammlung/aviews/embeddables.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
from django.shortcuts import get_object_or_404, render
|
||||||
|
from django.views.decorators.clickjacking import xframe_options_exempt
|
||||||
|
|
||||||
|
from fellchensammlung.aviews.helpers import headers
|
||||||
|
from fellchensammlung.models import RescueOrganization, AdoptionNotice, Species
|
||||||
|
|
||||||
|
|
||||||
|
@xframe_options_exempt
|
||||||
|
@headers({"X-Robots-Tag": "noindex"})
|
||||||
|
def list_ans_per_rescue_organization(request, rescue_organization_id, species_slug=None, active=True):
|
||||||
|
expand = request.GET.get("expand")
|
||||||
|
background_color = request.GET.get("background_color")
|
||||||
|
if expand is not None:
|
||||||
|
expand = True
|
||||||
|
else:
|
||||||
|
expand = False
|
||||||
|
org = get_object_or_404(RescueOrganization, pk=rescue_organization_id)
|
||||||
|
|
||||||
|
# Get only active adoption notices or all
|
||||||
|
if active:
|
||||||
|
adoption_notices_of_org = org.adoption_notices_in_hierarchy_divided_by_status[0]
|
||||||
|
else:
|
||||||
|
adoption_notices_of_org = org.adoption_notices
|
||||||
|
|
||||||
|
# Filter for Species if necessary
|
||||||
|
if species_slug is None:
|
||||||
|
adoption_notices = adoption_notices_of_org
|
||||||
|
else:
|
||||||
|
species = get_object_or_404(Species, slug=species_slug)
|
||||||
|
adoption_notices = [adoption_notice for adoption_notice in adoption_notices_of_org if
|
||||||
|
species in adoption_notice.species]
|
||||||
|
|
||||||
|
template = 'fellchensammlung/embeddables/list-adoption-notices.html'
|
||||||
|
return render(request, template,
|
||||||
|
context={"adoption_notices": adoption_notices,
|
||||||
|
"expand": expand,
|
||||||
|
"background_color": background_color})
|
||||||
23
src/fellchensammlung/aviews/helpers.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
def headers(headers):
|
||||||
|
"""Decorator adding arbitrary HTTP headers to the response.
|
||||||
|
|
||||||
|
This decorator adds HTTP headers specified in the argument (map), to the
|
||||||
|
HTTPResponse returned by the function being decorated.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
@headers({'Refresh': '10', 'X-Bender': 'Bite my shiny, metal ass!'})
|
||||||
|
def index(request):
|
||||||
|
....
|
||||||
|
Source: https://djangosnippets.org/snippets/275/
|
||||||
|
"""
|
||||||
|
def headers_wrapper(fun):
|
||||||
|
def wrapped_function(*args, **kwargs):
|
||||||
|
response = fun(*args, **kwargs)
|
||||||
|
for key in headers:
|
||||||
|
response[key] = headers[key]
|
||||||
|
return response
|
||||||
|
return wrapped_function
|
||||||
|
return headers_wrapper
|
||||||
|
|
||||||
12
src/fellchensammlung/aviews/urls.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from . import embeddables
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("tierschutzorganisationen/<int:rescue_organization_id>/vermittlungen/",
|
||||||
|
embeddables.list_ans_per_rescue_organization,
|
||||||
|
name="list-adoption-notices-for-rescue-organization"),
|
||||||
|
path("tierschutzorganisationen/<int:rescue_organization_id>/vermittlungen/<slug:species_slug>/",
|
||||||
|
embeddables.list_ans_per_rescue_organization,
|
||||||
|
name="list-adoption-notices-for-rescue-organization-species"),
|
||||||
|
]
|
||||||
@@ -57,6 +57,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:
|
||||||
@@ -129,6 +137,14 @@ class ModerationActionForm(forms.ModelForm):
|
|||||||
fields = ('action', 'public_comment', 'private_comment')
|
fields = ('action', 'public_comment', 'private_comment')
|
||||||
|
|
||||||
|
|
||||||
|
class AddedRegistrationForm(forms.Form):
|
||||||
|
captcha = forms.CharField(validators=[animal_validator], label=_("Nenne eine bekannte Tierart"), help_text=_(
|
||||||
|
"Bitte nenne hier eine bekannte Tierart (z.B. ein Tier das an der Leine geführt wird). Das Fragen wir dich um sicherzustellen, dass du kein Roboter bist."))
|
||||||
|
|
||||||
|
def signup(self, request, user):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class CustomRegistrationForm(RegistrationForm):
|
class CustomRegistrationForm(RegistrationForm):
|
||||||
class Meta(RegistrationForm.Meta):
|
class Meta(RegistrationForm.Meta):
|
||||||
model = User
|
model = User
|
||||||
@@ -153,5 +169,5 @@ class RescueOrgSearchForm(forms.Form):
|
|||||||
template_name = "fellchensammlung/forms/form_snippets.html"
|
template_name = "fellchensammlung/forms/form_snippets.html"
|
||||||
|
|
||||||
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.ONE_HUNDRED,
|
max_distance = forms.ChoiceField(choices=DistanceChoices, initial=DistanceChoices.TWENTY,
|
||||||
label=_("Suchradius"))
|
label=_("Suchradius"))
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-08-30 21:49
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fellchensammlung', '0059_rescueorganization_twenty_id_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='adoptionnotice',
|
||||||
|
options={'permissions': [('create_active_adoption_notice', 'Can create an active adoption notice')], 'verbose_name': 'Vermittlung', 'verbose_name_plural': 'Vermittlungen'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='adoptionnoticestatus',
|
||||||
|
options={'verbose_name': 'Vermittlungsstatus', 'verbose_name_plural': 'Vermittlungsstati'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='animal',
|
||||||
|
options={'verbose_name': 'Tier', 'verbose_name_plural': 'Tiere'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='announcement',
|
||||||
|
options={'verbose_name': 'Banner', 'verbose_name_plural': 'Banner'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='comment',
|
||||||
|
options={'verbose_name': 'Kommentar', 'verbose_name_plural': 'Kommentare'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='image',
|
||||||
|
options={'verbose_name': 'Bild', 'verbose_name_plural': 'Bilder'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='importantlocation',
|
||||||
|
options={'verbose_name': 'Wichtiger Standort', 'verbose_name_plural': 'Wichtige Standorte'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='location',
|
||||||
|
options={'verbose_name': 'Standort', 'verbose_name_plural': 'Standorte'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='moderationaction',
|
||||||
|
options={'verbose_name': 'Moderationsaktion', 'verbose_name_plural': 'Moderationsaktionen'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='notification',
|
||||||
|
options={'verbose_name': 'Benachrichtigung', 'verbose_name_plural': 'Benachrichtigungen'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='report',
|
||||||
|
options={'verbose_name': 'Meldung', 'verbose_name_plural': 'Meldungen'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='rescueorganization',
|
||||||
|
options={'ordering': ['name'], 'verbose_name': 'Tierschutzorganisation', 'verbose_name_plural': 'Tierschutzorganisationen'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='rule',
|
||||||
|
options={'verbose_name': 'Regel', 'verbose_name_plural': 'Regeln'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='searchsubscription',
|
||||||
|
options={'verbose_name': 'Abonnierte Suche', 'verbose_name_plural': 'Abonnierte Suchen'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='speciesspecificurl',
|
||||||
|
options={'verbose_name': 'Tierartspezifische URL', 'verbose_name_plural': 'Tierartspezifische URLs'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='subscriptions',
|
||||||
|
options={'verbose_name': 'Abonnement', 'verbose_name_plural': 'Abonnements'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='timestamp',
|
||||||
|
options={'verbose_name': 'Zeitstempel', 'verbose_name_plural': 'Zeitstempel'},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='adoptionnotice',
|
||||||
|
name='adoption_notice_status',
|
||||||
|
field=models.TextField(choices=[('active_searching', 'Searching'), ('active_interested', 'Interested'), ('awaiting_action_waiting_for_review', 'Waiting for review'), ('awaiting_action_needs_additional_info', 'Needs additional info'), ('closed_successful_with_notfellchen', 'Successful (with Notfellchen)'), ('closed_successful_without_notfellchen', 'Successful (without Notfellchen)'), ('closed_animal_died', 'Animal died'), ('closed_for_other_adoption_notice', 'Closed for other adoption notice'), ('closed_not_open_for_adoption_anymore', 'Not open for adoption anymore'), ('closed_other', 'Other (closed)'), ('disabled_against_the_rules', 'Against the rules'), ('disabled_unchecked', 'Unchecked'), ('disabled_other', 'Other (disabled)')], default='disabled_other', max_length=64, verbose_name='Status'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-08-30 21:51
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def map_status(adoption_notice_status):
|
||||||
|
minor = adoption_notice_status.minor_status
|
||||||
|
|
||||||
|
if minor == "searching":
|
||||||
|
return "active_searching"
|
||||||
|
if minor == "interested":
|
||||||
|
return "active_interested"
|
||||||
|
|
||||||
|
if minor == "waiting_for_review":
|
||||||
|
return "awaiting_action_waiting_for_review"
|
||||||
|
if minor == "needs_additional_info":
|
||||||
|
return "awaiting_action_needs_additional_info"
|
||||||
|
|
||||||
|
if minor == "successful_with_notfellchen":
|
||||||
|
return "closed_successful_with_notfellchen"
|
||||||
|
if minor == "successful_without_notfellchen":
|
||||||
|
return "closed_successful_without_notfellchen"
|
||||||
|
if minor == "animal_died":
|
||||||
|
return "closed_animal_died"
|
||||||
|
if minor == "closed_for_other_adoption_notice":
|
||||||
|
return "closed_for_other_adoption_notice"
|
||||||
|
if minor == "not_open_for_adoption_anymore":
|
||||||
|
return "closed_not_open_for_adoption_anymore"
|
||||||
|
if minor == "other":
|
||||||
|
return "closed_other"
|
||||||
|
|
||||||
|
if minor == "against_the_rules":
|
||||||
|
return "disabled_against_the_rules"
|
||||||
|
if minor == "unchecked":
|
||||||
|
return "disabled_unchecked"
|
||||||
|
if minor in ["missing_information", "technical_error"]:
|
||||||
|
return "disabled_other"
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_status(apps, schema_editor):
|
||||||
|
# We can't import the model directly as it may be a newer
|
||||||
|
# version than this migration expects. We use the historical version.
|
||||||
|
AdoptionNoticeStatus = apps.get_model("fellchensammlung", "AdoptionNoticeStatus")
|
||||||
|
AdoptionNotice = apps.get_model("fellchensammlung", "AdoptionNotice")
|
||||||
|
for ans in AdoptionNoticeStatus.objects.all():
|
||||||
|
adoption_notice = AdoptionNotice.objects.get(id=ans.adoption_notice.id)
|
||||||
|
new_status = map_status(ans)
|
||||||
|
logging.debug(f"{ans.minor_status} -> {new_status}")
|
||||||
|
adoption_notice.adoption_notice_status = map_status(ans)
|
||||||
|
adoption_notice.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('fellchensammlung', '0060_alter_adoptionnotice_options_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(migrate_status),
|
||||||
|
]
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-08-30 22:50
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fellchensammlung', '0061_datamigration_status_model_to_field'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='adoptionnotice',
|
||||||
|
name='adoption_notice_status',
|
||||||
|
field=models.TextField(choices=[('active_searching', 'Searching'), ('active_interested', 'Interested'), ('awaiting_action_waiting_for_review', 'Waiting for review'), ('awaiting_action_needs_additional_info', 'Needs additional info'), ('closed_successful_with_notfellchen', 'Successful (with Notfellchen)'), ('closed_successful_without_notfellchen', 'Successful (without Notfellchen)'), ('closed_animal_died', 'Animal died'), ('closed_for_other_adoption_notice', 'Closed for other adoption notice'), ('closed_not_open_for_adoption_anymore', 'Not open for adoption anymore'), ('closed_link_to_more_info_not_reachable', 'Der Link zu weiteren Informationen ist nicht mehr erreichbar.'), ('closed_other', 'Other (closed)'), ('disabled_against_the_rules', 'Against the rules'), ('disabled_unchecked', 'Unchecked'), ('disabled_other', 'Other (disabled)')], max_length=64, verbose_name='Status'),
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='AdoptionNoticeStatus',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-09-05 14:04
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fellchensammlung', '0062_alter_adoptionnotice_adoption_notice_status_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='adoptionnotice',
|
||||||
|
name='adoption_process',
|
||||||
|
field=models.TextField(blank=True, choices=[('contact_person_in_an', 'Kontaktiere die Person im Vermittlungstext')], max_length=64, null=True, verbose_name='Adoptionsprozess'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-09-06 11:11
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fellchensammlung', '0063_adoptionnotice_adoption_process'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='animal',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=200, verbose_name='Name'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='animal',
|
||||||
|
name='photos',
|
||||||
|
field=models.ManyToManyField(blank=True, to='fellchensammlung.image', verbose_name='Fotos'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='animal',
|
||||||
|
name='sex',
|
||||||
|
field=models.CharField(choices=[('F', 'Weiblich'), ('M', 'Männlich'), ('M_N', 'Männlich, kastriert'), ('F_N', 'Weiblich, kastriert'), ('I', 'Intergeschlechtlich')], max_length=20, verbose_name='Geschlecht'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='comment',
|
||||||
|
name='adoption_notice',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.adoptionnotice', verbose_name='Vermittlung'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='image',
|
||||||
|
name='alt_text',
|
||||||
|
field=models.TextField(help_text='Beschreibe das Bild für blinde und sehbehinderte Menschen', max_length=2000, verbose_name='Alternativtext'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='location',
|
||||||
|
name='city',
|
||||||
|
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Stadt'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='location',
|
||||||
|
name='county',
|
||||||
|
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Landkreis'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='location',
|
||||||
|
name='housenumber',
|
||||||
|
field=models.CharField(blank=True, max_length=20, null=True, verbose_name='Hausnummer'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='location',
|
||||||
|
name='latitude',
|
||||||
|
field=models.FloatField(verbose_name='Breitengrad'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='location',
|
||||||
|
name='longitude',
|
||||||
|
field=models.FloatField(verbose_name='Längengrad'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='location',
|
||||||
|
name='postcode',
|
||||||
|
field=models.CharField(blank=True, max_length=20, null=True, verbose_name='Postleitzahl'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='location',
|
||||||
|
name='street',
|
||||||
|
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Straße'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='notification',
|
||||||
|
name='user_to_notify',
|
||||||
|
field=models.ForeignKey(help_text='Useraccount der benachrichtigt wird', on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL, verbose_name='Empfänger*in'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='report',
|
||||||
|
name='created_at',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, verbose_name='Erstellt am'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='report',
|
||||||
|
name='updated_at',
|
||||||
|
field=models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert am'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='rule',
|
||||||
|
name='created_at',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, verbose_name='Erstellt am'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='rule',
|
||||||
|
name='language',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='fellchensammlung.language', verbose_name='Sprache'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='rule',
|
||||||
|
name='rule_identifier',
|
||||||
|
field=models.CharField(help_text='Ein eindeutiger Identifikator der Regel. Ein Regelobjekt derselben Regel in einer anderen Sprache muss den gleichen Identifikator haben', max_length=24, verbose_name='Regel-ID'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='rule',
|
||||||
|
name='rule_text',
|
||||||
|
field=models.TextField(verbose_name='Regeltext'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='rule',
|
||||||
|
name='updated_at',
|
||||||
|
field=models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert am'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='searchsubscription',
|
||||||
|
name='created_at',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, verbose_name='Erstellt am'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='searchsubscription',
|
||||||
|
name='sex',
|
||||||
|
field=models.CharField(choices=[('F', 'Weiblich'), ('M', 'Männlich'), ('M_N', 'Männlich, kastriert'), ('F_N', 'Weiblich Kastriert'), ('I', 'Intergeschlechtlich'), ('A', 'Alle')], max_length=20, verbose_name='Geschlecht'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='searchsubscription',
|
||||||
|
name='updated_at',
|
||||||
|
field=models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert am'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='subscriptions',
|
||||||
|
name='adoption_notice',
|
||||||
|
field=models.ForeignKey(help_text='Vermittlung die abonniert wurde', on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.adoptionnotice', verbose_name='Vermittlung'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='text',
|
||||||
|
name='title',
|
||||||
|
field=models.CharField(max_length=100, verbose_name='Titel'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
src/fellchensammlung/migrations/0065_species_slug.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-09-06 13:02
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fellchensammlung', '0064_alter_animal_name_alter_animal_photos_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='species',
|
||||||
|
name='slug',
|
||||||
|
field=models.SlugField(null=True, unique=True, verbose_name='Slug'),
|
||||||
|
),
|
||||||
|
]
|
||||||
20
src/fellchensammlung/migrations/0066_add_slug_to_species.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-09-06 13:05
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_slug(apps, schema_editor):
|
||||||
|
Species = apps.get_model("fellchensammlung", "Species")
|
||||||
|
for species in Species.objects.all():
|
||||||
|
species.slug = f"species-{species.id}"
|
||||||
|
species.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('fellchensammlung', '0065_species_slug'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(migrate_slug),
|
||||||
|
]
|
||||||
18
src/fellchensammlung/migrations/0067_alter_species_slug.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-09-06 13:12
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fellchensammlung', '0066_add_slug_to_species'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='species',
|
||||||
|
name='slug',
|
||||||
|
field=models.SlugField(unique=True, verbose_name='Slug'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-09-29 15:33
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fellchensammlung', '0067_alter_species_slug'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='adoptionnotice',
|
||||||
|
name='adoption_notice_status',
|
||||||
|
field=models.TextField(choices=[('active_searching', 'Searching'), ('active_interested', 'Interested'), ('awaiting_action_waiting_for_review', 'Waiting for review'), ('awaiting_action_needs_additional_info', 'Needs additional info'), ('awaiting_action_unchecked', 'Unchecked'), ('closed_successful_with_notfellchen', 'Successful (with Notfellchen)'), ('closed_successful_without_notfellchen', 'Successful (without Notfellchen)'), ('closed_animal_died', 'Animal died'), ('closed_for_other_adoption_notice', 'Closed for other adoption notice'), ('closed_not_open_for_adoption_anymore', 'Not open for adoption anymore'), ('closed_link_to_more_info_not_reachable', 'Der Link zu weiteren Informationen ist nicht mehr erreichbar.'), ('closed_other', 'Other (closed)'), ('disabled_against_the_rules', 'Against the rules'), ('disabled_other', 'Other (disabled)')], max_length=64, verbose_name='Status'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='image',
|
||||||
|
name='image',
|
||||||
|
field=models.ImageField(help_text='Wähle ein Bild aus', upload_to='images', verbose_name='Bild'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-10-20 08:43
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fellchensammlung', '0068_alter_adoptionnotice_adoption_notice_status_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rescueorganization',
|
||||||
|
name='regular_check_status',
|
||||||
|
field=models.CharField(choices=[('regular_check', 'Wird regelmäßig geprüft'), ('excluded_no_online_listing', 'Exkludiert: Tiere werden nicht online gelistet'), ('excluded_other_org', 'Exkludiert: Andere Organisation wird geprüft'), ('excluded_scope', 'Exkludiert: Organisation hat nie Notfellchen-relevanten Vermittlungen'), ('excluded_other', 'Exkludiert: Anderer Grund')], default='regular_check', help_text='Organisationen können, durch ändern dieser Einstellung, von der regelmäßigen Prüfung ausgeschlossen werden.', max_length=30, verbose_name='Status der regelmäßigen Prüfung'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,23 +1,19 @@
|
|||||||
import uuid
|
import uuid
|
||||||
from random import choices
|
|
||||||
from tabnanny import verbose
|
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.template.defaultfilters import slugify
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.dispatch import receiver
|
|
||||||
from django.db.models.signals import post_save
|
|
||||||
from django.contrib.auth.models import Group
|
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
|
||||||
|
|
||||||
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 age_as_hr_string, time_since_as_hr_string
|
from .tools.misc import time_since_as_hr_string
|
||||||
from .tools.model_helpers import NotificationTypeChoices
|
from .tools.model_helpers import NotificationTypeChoices, AdoptionNoticeStatusChoices, AdoptionProcess, \
|
||||||
|
AdoptionNoticeStatusChoicesDescriptions, RegularCheckStatusChoices
|
||||||
from .tools.model_helpers import ndm as NotificationDisplayMapping
|
from .tools.model_helpers import ndm as NotificationDisplayMapping
|
||||||
|
|
||||||
|
|
||||||
@@ -44,14 +40,14 @@ class Language(models.Model):
|
|||||||
|
|
||||||
class Location(models.Model):
|
class Location(models.Model):
|
||||||
place_id = models.CharField(max_length=200) # OSM id
|
place_id = models.CharField(max_length=200) # OSM id
|
||||||
latitude = models.FloatField()
|
latitude = models.FloatField(verbose_name=_("Breitengrad"))
|
||||||
longitude = models.FloatField()
|
longitude = models.FloatField(verbose_name=_("Längengrad"))
|
||||||
name = models.CharField(max_length=2000)
|
name = models.CharField(max_length=2000)
|
||||||
city = models.CharField(max_length=200, blank=True, null=True)
|
city = models.CharField(max_length=200, blank=True, null=True, verbose_name=_('Stadt'))
|
||||||
housenumber = models.CharField(max_length=20, blank=True, null=True)
|
housenumber = models.CharField(max_length=20, blank=True, null=True, verbose_name=_("Hausnummer"))
|
||||||
postcode = models.CharField(max_length=20, blank=True, null=True)
|
postcode = models.CharField(max_length=20, blank=True, null=True, verbose_name=_("Postleitzahl"))
|
||||||
street = models.CharField(max_length=200, blank=True, null=True)
|
street = models.CharField(max_length=200, blank=True, null=True, verbose_name=_("Straße"))
|
||||||
county = models.CharField(max_length=200, blank=True, null=True)
|
county = models.CharField(max_length=200, blank=True, null=True, verbose_name=_("Landkreis"))
|
||||||
# Country code as per ISO 3166-1 alpha-2
|
# Country code as per ISO 3166-1 alpha-2
|
||||||
# https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes
|
# https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes
|
||||||
countrycode = models.CharField(max_length=2, verbose_name=_("Ländercode"),
|
countrycode = models.CharField(max_length=2, verbose_name=_("Ländercode"),
|
||||||
@@ -135,6 +131,7 @@ class Species(models.Model):
|
|||||||
"""Model representing a species of animal."""
|
"""Model representing a species of animal."""
|
||||||
name = models.CharField(max_length=200, help_text=_('Name der Tierart'),
|
name = models.CharField(max_length=200, help_text=_('Name der Tierart'),
|
||||||
verbose_name=_('Name'))
|
verbose_name=_('Name'))
|
||||||
|
slug = models.SlugField(unique=True, verbose_name=_('Slug'))
|
||||||
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)
|
||||||
|
|
||||||
@@ -175,6 +172,12 @@ class RescueOrganization(models.Model):
|
|||||||
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."))
|
||||||
@@ -348,8 +351,9 @@ class User(AbstractUser):
|
|||||||
|
|
||||||
|
|
||||||
class Image(models.Model):
|
class Image(models.Model):
|
||||||
image = models.ImageField(upload_to='images')
|
image = models.ImageField(upload_to='images', verbose_name=_("Bild"), help_text=_("Wähle ein Bild aus"))
|
||||||
alt_text = models.TextField(max_length=2000, verbose_name=_('Alternativtext'))
|
alt_text = models.TextField(max_length=2000, verbose_name=_('Alternativtext'),
|
||||||
|
help_text=_("Beschreibe das Bild für blinde und sehbehinderte Menschen"))
|
||||||
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)
|
||||||
@@ -365,6 +369,11 @@ class Image(models.Model):
|
|||||||
def as_html(self):
|
def as_html(self):
|
||||||
return f'<img src="{MEDIA_URL}/{self.image}" alt="{self.alt_text}">'
|
return f'<img src="{MEDIA_URL}/{self.image}" alt="{self.alt_text}">'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def as_base64(self):
|
||||||
|
encoded_string = base64.b64encode(self.image.file.read())
|
||||||
|
return encoded_string.decode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
class AdoptionNotice(models.Model):
|
class AdoptionNotice(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -395,6 +404,11 @@ class AdoptionNotice(models.Model):
|
|||||||
location_string = models.CharField(max_length=200, verbose_name=_("Ortsangabe"))
|
location_string = models.CharField(max_length=200, verbose_name=_("Ortsangabe"))
|
||||||
location = models.ForeignKey(Location, blank=True, null=True, on_delete=models.SET_NULL, )
|
location = models.ForeignKey(Location, blank=True, null=True, on_delete=models.SET_NULL, )
|
||||||
owner = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('Creator'))
|
owner = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('Creator'))
|
||||||
|
adoption_notice_status = models.TextField(max_length=64, verbose_name=_('Status'),
|
||||||
|
choices=AdoptionNoticeStatusChoices.all_choices())
|
||||||
|
adoption_process = models.TextField(null=True, blank=True,
|
||||||
|
max_length=64, verbose_name=_('Adoptionsprozess'),
|
||||||
|
choices=AdoptionProcess)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def animals(self):
|
def animals(self):
|
||||||
@@ -414,6 +428,13 @@ class AdoptionNotice(models.Model):
|
|||||||
num_per_sex[sex] = self.animals.filter(sex=sex).count()
|
num_per_sex[sex] = self.animals.filter(sex=sex).count()
|
||||||
return num_per_sex
|
return num_per_sex
|
||||||
|
|
||||||
|
@property
|
||||||
|
def species(self):
|
||||||
|
species = set()
|
||||||
|
for animal in self.animals:
|
||||||
|
species.add(animal.species)
|
||||||
|
return species
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def last_checked_hr(self):
|
def last_checked_hr(self):
|
||||||
time_since_last_checked = timezone.now() - self.last_checked
|
time_since_last_checked = timezone.now() - self.last_checked
|
||||||
@@ -443,12 +464,21 @@ class AdoptionNotice(models.Model):
|
|||||||
else:
|
else:
|
||||||
return self.location.latitude, self.location.longitude
|
return self.location.latitude, self.location.longitude
|
||||||
|
|
||||||
@property
|
def _get_short_description(self, length: int) -> str:
|
||||||
def description_short(self):
|
|
||||||
if self.description is None:
|
if self.description is None:
|
||||||
return ""
|
return ""
|
||||||
if len(self.description) > 200:
|
elif len(self.description) > length:
|
||||||
return self.description[:200] + f" ... [weiterlesen]({self.get_absolute_url()})"
|
return self.description[:length] + f" ... [weiterlesen]({self.get_absolute_url()})"
|
||||||
|
else:
|
||||||
|
return self.description
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description_short(self):
|
||||||
|
return self._get_short_description(200)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description_100_short(self):
|
||||||
|
return self._get_short_description(90)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
"""Returns the url to access a detailed page for the adoption notice."""
|
"""Returns the url to access a detailed page for the adoption notice."""
|
||||||
@@ -510,60 +540,34 @@ class AdoptionNotice(models.Model):
|
|||||||
"""
|
"""
|
||||||
return geo.object_in_distance(self, position, max_distance, unknown_true)
|
return geo.object_in_distance(self, position, max_distance, unknown_true)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _values_of(list_of_enums):
|
||||||
|
return list(map(lambda x: x[0], list_of_enums))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_active(self):
|
def is_active(self):
|
||||||
if not hasattr(self, 'adoptionnoticestatus'):
|
return self.adoption_notice_status in self._values_of(AdoptionNoticeStatusChoices.Active.choices)
|
||||||
return False
|
|
||||||
return self.adoptionnoticestatus.is_active
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_disabled(self):
|
def is_disabled(self):
|
||||||
if not hasattr(self, 'adoptionnoticestatus'):
|
return self.adoption_notice_status in self._values_of(AdoptionNoticeStatusChoices.Disabled.choices)
|
||||||
return False
|
|
||||||
return self.adoptionnoticestatus.is_disabled
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_closed(self):
|
def is_closed(self):
|
||||||
if not hasattr(self, 'adoptionnoticestatus'):
|
return self.adoption_notice_status in self._values_of(AdoptionNoticeStatusChoices.Closed.choices)
|
||||||
return False
|
|
||||||
return self.adoptionnoticestatus.is_closed
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_interested(self):
|
|
||||||
if not hasattr(self, 'adoptionnoticestatus'):
|
|
||||||
return False
|
|
||||||
return self.adoptionnoticestatus.is_interested
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_awaiting_action(self):
|
def is_awaiting_action(self):
|
||||||
if not hasattr(self, 'adoptionnoticestatus'):
|
return self.adoption_notice_status in self._values_of(AdoptionNoticeStatusChoices.AwaitingAction.choices)
|
||||||
return False
|
|
||||||
return self.adoptionnoticestatus.is_awaiting_action
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_disabled_unchecked(self):
|
def status_description(self):
|
||||||
if not hasattr(self, 'adoptionnoticestatus'):
|
return AdoptionNoticeStatusChoicesDescriptions.mapping[self.adoption_notice_status]
|
||||||
return False
|
|
||||||
return self.adoptionnoticestatus.is_disabled_unchecked
|
|
||||||
|
|
||||||
def set_closed(self, minor_status=None):
|
|
||||||
self.last_checked = timezone.now()
|
|
||||||
self.save()
|
|
||||||
self.adoptionnoticestatus.set_closed(minor_status)
|
|
||||||
|
|
||||||
def set_active(self):
|
|
||||||
self.last_checked = timezone.now()
|
|
||||||
self.save()
|
|
||||||
if not hasattr(self, 'adoptionnoticestatus'):
|
|
||||||
AdoptionNoticeStatus.create_other(self)
|
|
||||||
self.adoptionnoticestatus.set_active()
|
|
||||||
|
|
||||||
def set_unchecked(self):
|
def set_unchecked(self):
|
||||||
self.last_checked = timezone.now()
|
self.last_checked = timezone.now()
|
||||||
|
self.adoption_notice_status = AdoptionNoticeStatusChoices.AwaitingAction.UNCHECKED
|
||||||
self.save()
|
self.save()
|
||||||
if not hasattr(self, 'adoptionnoticestatus'):
|
|
||||||
AdoptionNoticeStatus.create_other(self)
|
|
||||||
self.adoptionnoticestatus.set_unchecked()
|
|
||||||
|
|
||||||
for subscription in self.get_subscriptions():
|
for subscription in self.get_subscriptions():
|
||||||
notification_title = _("Vermittlung deaktiviert:") + f" {self.name}"
|
notification_title = _("Vermittlung deaktiviert:") + f" {self.name}"
|
||||||
@@ -583,124 +587,6 @@ class AdoptionNotice(models.Model):
|
|||||||
return last_post.created_at
|
return last_post.created_at
|
||||||
|
|
||||||
|
|
||||||
class AdoptionNoticeStatus(models.Model):
|
|
||||||
"""
|
|
||||||
The major status indicates a general state of an adoption notice
|
|
||||||
whereas the minor status is used for reporting
|
|
||||||
"""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('Vermittlungsstatus')
|
|
||||||
verbose_name_plural = _('Vermittlungsstati')
|
|
||||||
|
|
||||||
ACTIVE = "active"
|
|
||||||
AWAITING_ACTION = "awaiting_action"
|
|
||||||
CLOSED = "closed"
|
|
||||||
DISABLED = "disabled"
|
|
||||||
MAJOR_STATUS_CHOICES = {
|
|
||||||
ACTIVE: "active",
|
|
||||||
AWAITING_ACTION: "in review",
|
|
||||||
CLOSED: "closed",
|
|
||||||
DISABLED: "disabled",
|
|
||||||
}
|
|
||||||
|
|
||||||
MINOR_STATUS_CHOICES = {
|
|
||||||
ACTIVE: {
|
|
||||||
"searching": "searching",
|
|
||||||
"interested": "interested",
|
|
||||||
},
|
|
||||||
AWAITING_ACTION: {
|
|
||||||
"waiting_for_review": "waiting_for_review",
|
|
||||||
"needs_additional_info": "needs_additional_info",
|
|
||||||
},
|
|
||||||
CLOSED: {
|
|
||||||
"successful_with_notfellchen": "successful_with_notfellchen",
|
|
||||||
"successful_without_notfellchen": "successful_without_notfellchen",
|
|
||||||
"animal_died": "animal_died",
|
|
||||||
"closed_for_other_adoption_notice": "closed_for_other_adoption_notice",
|
|
||||||
"not_open_for_adoption_anymore": "not_open_for_adoption_anymore",
|
|
||||||
"other": "other"
|
|
||||||
},
|
|
||||||
DISABLED: {
|
|
||||||
"against_the_rules": "against_the_rules",
|
|
||||||
"missing_information": "missing_information",
|
|
||||||
"technical_error": "technical_error",
|
|
||||||
"unchecked": "unchecked",
|
|
||||||
"other": "other"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
major_status = models.CharField(choices=MAJOR_STATUS_CHOICES, max_length=200)
|
|
||||||
minor_choices = {}
|
|
||||||
for key in MINOR_STATUS_CHOICES:
|
|
||||||
minor_choices.update(MINOR_STATUS_CHOICES[key])
|
|
||||||
minor_status = models.CharField(choices=minor_choices, max_length=200)
|
|
||||||
adoption_notice = models.OneToOneField(AdoptionNotice, on_delete=models.CASCADE)
|
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.adoption_notice}: {self.major_status}, {self.minor_status}"
|
|
||||||
|
|
||||||
def as_string(self):
|
|
||||||
return f"{self.major_status}, {self.minor_status}"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_active(self):
|
|
||||||
return self.major_status == self.ACTIVE
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_disabled(self):
|
|
||||||
return self.major_status == self.DISABLED
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_closed(self):
|
|
||||||
return self.major_status == self.CLOSED
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_awaiting_action(self):
|
|
||||||
return self.major_status == self.AWAITING_ACTION
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_interested(self):
|
|
||||||
return self.major_status == self.ACTIVE and self.minor_status == "interested"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_disabled_unchecked(self):
|
|
||||||
return self.major_status == self.DISABLED and self.minor_status == "unchecked"
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_minor_choices(major_status):
|
|
||||||
return AdoptionNoticeStatus.MINOR_STATUS_CHOICES[major_status]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_other(an_instance):
|
|
||||||
# Used as empty status to be changed immediately
|
|
||||||
major_status = AdoptionNoticeStatus.DISABLED
|
|
||||||
minor_status = AdoptionNoticeStatus.MINOR_STATUS_CHOICES[AdoptionNoticeStatus.DISABLED]["other"]
|
|
||||||
AdoptionNoticeStatus.objects.create(major_status=major_status,
|
|
||||||
minor_status=minor_status,
|
|
||||||
adoption_notice=an_instance)
|
|
||||||
|
|
||||||
def set_closed(self, minor_status=None):
|
|
||||||
self.major_status = self.MAJOR_STATUS_CHOICES[self.CLOSED]
|
|
||||||
if minor_status is None:
|
|
||||||
self.minor_status = self.MINOR_STATUS_CHOICES[self.CLOSED]["other"]
|
|
||||||
else:
|
|
||||||
self.minor_status = self.MINOR_STATUS_CHOICES[self.CLOSED][minor_status]
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
def set_unchecked(self):
|
|
||||||
self.major_status = self.MAJOR_STATUS_CHOICES[self.DISABLED]
|
|
||||||
self.minor_status = self.MINOR_STATUS_CHOICES[self.DISABLED]["unchecked"]
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
def set_active(self):
|
|
||||||
self.major_status = self.MAJOR_STATUS_CHOICES[self.ACTIVE]
|
|
||||||
self.minor_status = self.MINOR_STATUS_CHOICES[self.ACTIVE]["searching"]
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
|
|
||||||
class SexChoices(models.TextChoices):
|
class SexChoices(models.TextChoices):
|
||||||
FEMALE = "F", _("Weiblich")
|
FEMALE = "F", _("Weiblich")
|
||||||
MALE = "M", _("Männlich")
|
MALE = "M", _("Männlich")
|
||||||
@@ -724,13 +610,14 @@ class Animal(models.Model):
|
|||||||
verbose_name_plural = _('Tiere')
|
verbose_name_plural = _('Tiere')
|
||||||
|
|
||||||
date_of_birth = models.DateField(verbose_name=_('Geburtsdatum'))
|
date_of_birth = models.DateField(verbose_name=_('Geburtsdatum'))
|
||||||
name = models.CharField(max_length=200)
|
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"))
|
||||||
photos = models.ManyToManyField(Image, blank=True)
|
photos = models.ManyToManyField(Image, blank=True, verbose_name=_("Fotos"))
|
||||||
sex = models.CharField(
|
sex = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=SexChoices.choices,
|
choices=SexChoices.choices,
|
||||||
|
verbose_name=_("Geschlecht")
|
||||||
)
|
)
|
||||||
adoption_notice = models.ForeignKey(AdoptionNotice, on_delete=models.CASCADE)
|
adoption_notice = models.ForeignKey(AdoptionNotice, on_delete=models.CASCADE)
|
||||||
owner = models.ForeignKey(User, on_delete=models.CASCADE)
|
owner = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
@@ -795,10 +682,10 @@ class SearchSubscription(models.Model):
|
|||||||
|
|
||||||
owner = models.ForeignKey(User, on_delete=models.CASCADE)
|
owner = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
location = models.ForeignKey(Location, on_delete=models.PROTECT, null=True)
|
location = models.ForeignKey(Location, on_delete=models.PROTECT, null=True)
|
||||||
sex = models.CharField(max_length=20, choices=SexChoicesWithAll.choices)
|
sex = models.CharField(max_length=20, choices=SexChoicesWithAll.choices, verbose_name=_("Geschlecht"))
|
||||||
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)
|
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Zuletzt geändert am"))
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Erstellt am"))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.location and self.max_distance:
|
if self.location and self.max_distance:
|
||||||
@@ -819,12 +706,16 @@ class Rule(models.Model):
|
|||||||
title = models.CharField(max_length=200)
|
title = models.CharField(max_length=200)
|
||||||
|
|
||||||
# Markdown is allowed in rule text
|
# Markdown is allowed in rule text
|
||||||
rule_text = models.TextField()
|
rule_text = models.TextField(verbose_name=_("Regeltext"))
|
||||||
language = models.ForeignKey(Language, on_delete=models.PROTECT)
|
language = models.ForeignKey(Language, on_delete=models.PROTECT, verbose_name=_("Sprache"))
|
||||||
# Rule identifier allows to translate rules with the same identifier
|
# Rule identifier allows to translate rules with the same identifier
|
||||||
rule_identifier = models.CharField(max_length=24)
|
rule_identifier = models.CharField(max_length=24,
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
verbose_name=_("Regel-ID"),
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
help_text=_("Ein eindeutiger Identifikator der Regel. Ein Regelobjekt "
|
||||||
|
"derselben Regel in einer anderen Sprache muss den gleichen "
|
||||||
|
"Identifikator haben"))
|
||||||
|
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Zuletzt geändert am"))
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Erstellt am"))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
@@ -848,8 +739,8 @@ class Report(models.Model):
|
|||||||
status = models.CharField(max_length=30, choices=STATES)
|
status = models.CharField(max_length=30, choices=STATES)
|
||||||
reported_broken_rules = models.ManyToManyField(Rule, verbose_name=_("Regeln gegen die verstoßen wurde"))
|
reported_broken_rules = models.ManyToManyField(Rule, verbose_name=_("Regeln gegen die verstoßen wurde"))
|
||||||
user_comment = models.TextField(blank=True, verbose_name=_("Kommentar/Zusätzliche Information"))
|
user_comment = models.TextField(blank=True, verbose_name=_("Kommentar/Zusätzliche Information"))
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Zuletzt geändert am"))
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Erstellt am"))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"[{self.status}]: {self.user_comment:.20}"
|
return f"[{self.status}]: {self.user_comment:.20}"
|
||||||
@@ -953,7 +844,7 @@ class Text(models.Model):
|
|||||||
"""
|
"""
|
||||||
Base class to store markdown content
|
Base class to store markdown content
|
||||||
"""
|
"""
|
||||||
title = models.CharField(max_length=100)
|
title = models.CharField(max_length=100, verbose_name=_("Titel"))
|
||||||
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)
|
||||||
@@ -1043,7 +934,7 @@ class Comment(models.Model):
|
|||||||
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('Nutzer*in'))
|
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('Nutzer*in'))
|
||||||
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)
|
||||||
adoption_notice = models.ForeignKey(AdoptionNotice, on_delete=models.CASCADE, verbose_name=_('AdoptionNotice'))
|
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)
|
||||||
|
|
||||||
@@ -1071,7 +962,7 @@ class Notification(models.Model):
|
|||||||
user_to_notify = models.ForeignKey(User,
|
user_to_notify = models.ForeignKey(User,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
verbose_name=_('Empfänger*in'),
|
verbose_name=_('Empfänger*in'),
|
||||||
help_text=_("Useraccount der Benachrichtigt wird"),
|
help_text=_("Useraccount der benachrichtigt wird"),
|
||||||
related_name='user')
|
related_name='user')
|
||||||
title = models.CharField(max_length=100, verbose_name=_("Titel"))
|
title = models.CharField(max_length=100, verbose_name=_("Titel"))
|
||||||
text = models.TextField(verbose_name="Inhalt")
|
text = models.TextField(verbose_name="Inhalt")
|
||||||
@@ -1113,7 +1004,8 @@ class Subscriptions(models.Model):
|
|||||||
verbose_name_plural = _("Abonnements")
|
verbose_name_plural = _("Abonnements")
|
||||||
|
|
||||||
owner = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('Nutzer*in'))
|
owner = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('Nutzer*in'))
|
||||||
adoption_notice = models.ForeignKey(AdoptionNotice, on_delete=models.CASCADE, verbose_name=_('AdoptionNotice'))
|
adoption_notice = models.ForeignKey(AdoptionNotice, on_delete=models.CASCADE, verbose_name=_('Vermittlung'),
|
||||||
|
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)
|
||||||
|
|
||||||
@@ -1181,7 +1073,7 @@ class SocialMediaPost(models.Model):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def get_an_to_post():
|
def get_an_to_post():
|
||||||
adoption_notices_without_post = AdoptionNotice.objects.filter(socialmediapost__isnull=True,
|
adoption_notices_without_post = AdoptionNotice.objects.filter(socialmediapost__isnull=True,
|
||||||
adoptionnoticestatus__major_status=AdoptionNoticeStatus.ACTIVE)
|
adoption_notice_status__in=AdoptionNoticeStatusChoices.Active.values)
|
||||||
return adoption_notices_without_post.first()
|
return adoption_notices_without_post.first()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ $confirm: hsl(133deg, 100%, calc(41% + 0%));
|
|||||||
|
|
||||||
p > a {
|
p > a {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
p > a.button {
|
p > a.button {
|
||||||
@@ -234,7 +235,7 @@ IMAGES
|
|||||||
|
|
||||||
.thumbnail img {
|
.thumbnail img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 50px;
|
height: 70px;
|
||||||
object-fit: cover; /* Crops the images */
|
object-fit: cover; /* Crops the images */
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
@@ -346,3 +347,17 @@ AN Cards
|
|||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Embedding Specifics */
|
||||||
|
.embed-main-content {
|
||||||
|
padding: 20px 10px 20px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FLOATING BUTTON
|
||||||
|
|
||||||
|
.floating {
|
||||||
|
position: fixed;
|
||||||
|
border-radius: 0.3rem;
|
||||||
|
bottom: 4.5rem;
|
||||||
|
right: 1rem;
|
||||||
|
}
|
||||||
@@ -1,423 +0,0 @@
|
|||||||
function getCookie(name) {
|
|
||||||
let cookieValue = null;
|
|
||||||
if (document.cookie && document.cookie !== '') {
|
|
||||||
const cookies = document.cookie.split(';');
|
|
||||||
for (let i = 0; i < cookies.length; i++) {
|
|
||||||
const cookie = cookies[i].trim();
|
|
||||||
// Does this cookie string begin with the name we want?
|
|
||||||
if (cookie.substring(0, name.length + 1) === (name + '=')) {
|
|
||||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cookieValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
|
||||||
// ------------------------------------------------ functions
|
|
||||||
var show = function (elem) {
|
|
||||||
// Get the natural height of the element
|
|
||||||
var getHeight = function () {
|
|
||||||
elem.style.display = 'block'; // Make it visible
|
|
||||||
var height = elem.scrollHeight + 'px'; // Get its height
|
|
||||||
elem.style.display = ''; // Hide it again
|
|
||||||
return height;
|
|
||||||
};
|
|
||||||
var height = getHeight(); // Get the natural height
|
|
||||||
elem.classList.remove('closed');
|
|
||||||
elem.classList.add('open'); // Make the element visible
|
|
||||||
elem.setAttribute('aria-hidden', 'false');
|
|
||||||
elem.style.height = height; // Update the max-height
|
|
||||||
// Once the transition is complete, remove the inline max-height so the content can scale responsively
|
|
||||||
window.setTimeout(function () {
|
|
||||||
elem.style.height = '';
|
|
||||||
}, 500);
|
|
||||||
};
|
|
||||||
|
|
||||||
var hide = function (elem) {
|
|
||||||
// Give the element a height to change from
|
|
||||||
elem.style.height = elem.scrollHeight + 'px';
|
|
||||||
// Set the height back to 0
|
|
||||||
window.setTimeout(function () {
|
|
||||||
elem.style.height = '0';
|
|
||||||
}, 1);
|
|
||||||
// When the transition is complete, hide it
|
|
||||||
window.setTimeout(function () {
|
|
||||||
elem.classList.remove('open');
|
|
||||||
elem.classList.add('closed');
|
|
||||||
elem.setAttribute('aria-hidden', 'true');
|
|
||||||
}, 500);
|
|
||||||
};
|
|
||||||
|
|
||||||
var toggle = function (elem, timing) {
|
|
||||||
// If the element is visible, hide it
|
|
||||||
if (elem.classList.contains('open')) {
|
|
||||||
hide(elem);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Otherwise, show it
|
|
||||||
show(elem);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// ------------------------------------------------ build form
|
|
||||||
let orig_form = document.querySelector('form');
|
|
||||||
orig_form.style.display = 'none';
|
|
||||||
|
|
||||||
let an_max = 6;
|
|
||||||
|
|
||||||
let an_fieldset = document.createElement('fieldset');
|
|
||||||
an_fieldset.classList.add('cell');
|
|
||||||
let an_fieldset_legend = document.createElement('legend');
|
|
||||||
an_fieldset_legend.innerHTML = "Allgemeines";
|
|
||||||
an_fieldset.appendChild(an_fieldset_legend);
|
|
||||||
|
|
||||||
let an_name = document.createElement('input');
|
|
||||||
an_name.setAttribute('type', 'text');
|
|
||||||
an_name.setAttribute('name', 'name');
|
|
||||||
an_name.setAttribute('class', 'input');
|
|
||||||
an_name.setAttribute('maxlength', 200);
|
|
||||||
an_name.setAttribute('required', 'required');
|
|
||||||
let an_name_label = document.createElement('label');
|
|
||||||
an_name_label.setAttribute('class', 'label');
|
|
||||||
an_name_label.innerHTML = 'Titel der Vermittlung';
|
|
||||||
an_name_label.appendChild(an_name);
|
|
||||||
|
|
||||||
let an_location_string = document.createElement('input');
|
|
||||||
an_location_string.setAttribute('type', 'text');
|
|
||||||
an_location_string.setAttribute('name', 'location_string');
|
|
||||||
an_location_string.setAttribute('class', 'input');
|
|
||||||
an_location_string.setAttribute('maxlength', 200);
|
|
||||||
an_location_string.setAttribute('required', 'required');
|
|
||||||
let an_location_string_label = document.createElement('label');
|
|
||||||
an_location_string_label.setAttribute('class', 'label');
|
|
||||||
an_location_string_label.innerHTML = 'Ortsangabe';
|
|
||||||
an_location_string_label.appendChild(an_location_string);
|
|
||||||
|
|
||||||
let an_further_information = document.createElement('input');
|
|
||||||
an_further_information.setAttribute('type', 'url');
|
|
||||||
an_further_information.setAttribute('name', 'further_information');
|
|
||||||
an_further_information.setAttribute('class', 'input');
|
|
||||||
an_further_information.setAttribute('maxlength', 200);
|
|
||||||
let an_further_information_label = document.createElement('label');
|
|
||||||
an_further_information_label.setAttribute('class', 'label');
|
|
||||||
an_further_information_label.innerHTML = 'Link zu mehr Informationen';
|
|
||||||
an_further_information_label.appendChild(an_further_information);
|
|
||||||
|
|
||||||
let an_species = document.createElement('select');
|
|
||||||
let an_species_rat = document.createElement('option');
|
|
||||||
an_species_rat.value = 1;
|
|
||||||
an_species_rat.innerHTML = "Farbratte";
|
|
||||||
an_species.appendChild(an_species_rat);
|
|
||||||
an_species.setAttribute('name', 'species');
|
|
||||||
an_species.setAttribute('class', 'input');
|
|
||||||
an_species.setAttribute('required', 'required');
|
|
||||||
let an_species_label = document.createElement('label');
|
|
||||||
an_species_label.setAttribute('class', 'label');
|
|
||||||
an_species_label.innerHTML = 'Tierart';
|
|
||||||
an_species_label.appendChild(an_species);
|
|
||||||
|
|
||||||
let an_number = document.createElement('input');
|
|
||||||
an_number.setAttribute('type', 'number');
|
|
||||||
an_number.setAttribute('name', 'number');
|
|
||||||
an_number.setAttribute('class', 'input');
|
|
||||||
an_number.setAttribute('min', 1);
|
|
||||||
an_number.setAttribute('max', an_max);
|
|
||||||
an_number.setAttribute('required', 'required');
|
|
||||||
let an_number_label = document.createElement('label');
|
|
||||||
an_number_label.setAttribute('class', 'label');
|
|
||||||
an_number_label.innerHTML = 'Anzahl Tiere';
|
|
||||||
an_number_label.appendChild(an_number);
|
|
||||||
|
|
||||||
let an_dateofbirth = document.createElement('input');
|
|
||||||
an_dateofbirth.setAttribute('type', 'date');
|
|
||||||
an_dateofbirth.setAttribute('name', 'dateofbirth');
|
|
||||||
an_dateofbirth.setAttribute('class', 'input');
|
|
||||||
an_dateofbirth.setAttribute('maxlength', 200);
|
|
||||||
an_dateofbirth.setAttribute('required', 'required');
|
|
||||||
let an_dateofbirth_label = document.createElement('label');
|
|
||||||
an_dateofbirth_label.setAttribute('class', 'label');
|
|
||||||
an_dateofbirth_label.innerHTML = 'Geburtsdatum';
|
|
||||||
an_dateofbirth_label.appendChild(an_dateofbirth);
|
|
||||||
|
|
||||||
let an_sex = document.createElement('select');
|
|
||||||
let an_sex_F = document.createElement('option');
|
|
||||||
an_sex_F.value = 'F';
|
|
||||||
an_sex_F.innerHTML = "Weiblich";
|
|
||||||
an_sex.appendChild(an_sex_F);
|
|
||||||
let an_sex_M = document.createElement('option');
|
|
||||||
an_sex_M.value = 'M';
|
|
||||||
an_sex_M.innerHTML = "Männlich";
|
|
||||||
an_sex.appendChild(an_sex_M);
|
|
||||||
let an_sex_F_N = document.createElement('option');
|
|
||||||
an_sex_F_N.value = 'F_N';
|
|
||||||
an_sex_F_N.innerHTML = "Weiblich, kastriert";
|
|
||||||
an_sex.appendChild(an_sex_F_N);
|
|
||||||
let an_sex_M_N = document.createElement('option');
|
|
||||||
an_sex_M_N.value = 'M_N';
|
|
||||||
an_sex_M_N.innerHTML = "Männlich, kastriert";
|
|
||||||
an_sex.appendChild(an_sex_M_N);
|
|
||||||
let an_sex_I = document.createElement('option');
|
|
||||||
an_sex_I.value = 'I';
|
|
||||||
an_sex_I.innerHTML = "Intergeschlechtlich";
|
|
||||||
an_sex.appendChild(an_sex_I);
|
|
||||||
an_sex.setAttribute('name', 'sex');
|
|
||||||
an_sex.setAttribute('class', 'input');
|
|
||||||
an_sex.setAttribute('required', 'required');
|
|
||||||
let an_sex_label = document.createElement('label');
|
|
||||||
an_sex_label.setAttribute('class', 'label');
|
|
||||||
an_sex_label.innerHTML = 'Geschlecht';
|
|
||||||
an_sex_label.appendChild(an_sex);
|
|
||||||
|
|
||||||
let an_searching_since = document.createElement('input');
|
|
||||||
an_searching_since.setAttribute('type', 'date');
|
|
||||||
an_searching_since.setAttribute('name', 'searching_since');
|
|
||||||
an_searching_since.setAttribute('class', 'input');
|
|
||||||
an_searching_since.setAttribute('maxlength', 200);
|
|
||||||
an_searching_since.setAttribute('required', 'required');
|
|
||||||
let an_searching_since_label = document.createElement('label');
|
|
||||||
an_searching_since_label.setAttribute('class', 'label');
|
|
||||||
an_searching_since_label.innerHTML = 'neues Zuhause gesucht seit';
|
|
||||||
an_searching_since_label.appendChild(an_searching_since);
|
|
||||||
|
|
||||||
let an_group_only = document.createElement('select');
|
|
||||||
let an_group_only_yes = document.createElement('option');
|
|
||||||
an_group_only_yes.value = 1;
|
|
||||||
an_group_only_yes.innerHTML = "nur zusammen";
|
|
||||||
let an_group_only_no = document.createElement('option');
|
|
||||||
an_group_only_no.value = 0;
|
|
||||||
an_group_only_no.innerHTML = "auch einzeln";
|
|
||||||
an_group_only.appendChild(an_group_only_yes);
|
|
||||||
an_group_only.appendChild(an_group_only_no);
|
|
||||||
an_group_only.setAttribute('name', 'group_only');
|
|
||||||
an_group_only.setAttribute('class', 'input');
|
|
||||||
an_group_only.setAttribute('required', 'required');
|
|
||||||
let an_group_only_label = document.createElement('label');
|
|
||||||
an_group_only_label.setAttribute('class', 'label');
|
|
||||||
an_group_only_label.innerHTML = 'Gruppenvermittlung';
|
|
||||||
an_group_only_label.appendChild(an_group_only);
|
|
||||||
|
|
||||||
|
|
||||||
let animals = document.createElement('fieldset');
|
|
||||||
animals.classList.add('cell', 'is-col-span-2');
|
|
||||||
let animals_legend = document.createElement('legend');
|
|
||||||
animals_legend.innerHTML = 'Angaben zu den Tieren';
|
|
||||||
animals.appendChild(animals_legend);
|
|
||||||
let noteNumber = document.createElement('p');
|
|
||||||
noteNumber.setAttribute('id', 'noteNumber');
|
|
||||||
noteNumber.innerHTML = 'Bitte Anzahl Tiere angeben';
|
|
||||||
animals.appendChild(noteNumber);
|
|
||||||
|
|
||||||
let an_description = document.createElement('textarea');
|
|
||||||
an_description.setAttribute('name', 'an_description');
|
|
||||||
an_description.classList.add('input', 'textarea');
|
|
||||||
let an_description_label = document.createElement('label');
|
|
||||||
an_description_label.innerHTML = 'Beschreibung der Gruppe';
|
|
||||||
an_description_label.classList.add('label');
|
|
||||||
an_description_label.appendChild(an_description);
|
|
||||||
animals.appendChild(an_group_only_label);
|
|
||||||
animals.appendChild(an_description_label);
|
|
||||||
|
|
||||||
for (let i = 0; i < an_max; i++) {
|
|
||||||
let an_fieldset_$i = document.createElement('fieldset');
|
|
||||||
an_fieldset_$i.classList.add('animal-' + i, 'animal');
|
|
||||||
an_fieldset_$i.appendChild(document.createElement('legend'));
|
|
||||||
an_fieldset_$i.querySelector('legend').innerHTML = 'Tier ' + parseInt(i + 1);
|
|
||||||
let an_name_$i = document.createElement('input');
|
|
||||||
an_name_$i.setAttribute('type', 'text');
|
|
||||||
an_name_$i.setAttribute('name', 'name-' + i);
|
|
||||||
an_name_$i.setAttribute('class', 'input');
|
|
||||||
an_name_$i.setAttribute('maxlength', 200);
|
|
||||||
an_name_$i.setAttribute('required', 'required');
|
|
||||||
let an_name_$i_label = document.createElement('label');
|
|
||||||
an_name_$i_label.setAttribute('class', 'label');
|
|
||||||
an_name_$i_label.innerHTML = 'Name';
|
|
||||||
an_name_$i_label.appendChild(an_name_$i);
|
|
||||||
|
|
||||||
let an_dateofbirth_$i = document.createElement('input');
|
|
||||||
an_dateofbirth_$i.setAttribute('type', 'date');
|
|
||||||
an_dateofbirth_$i.setAttribute('name', 'dateofbirth');
|
|
||||||
an_dateofbirth_$i.setAttribute('class', 'input');
|
|
||||||
an_dateofbirth_$i.setAttribute('maxlength', 200);
|
|
||||||
an_dateofbirth_$i.setAttribute('required', 'required');
|
|
||||||
let an_dateofbirth_$i_label = document.createElement('label');
|
|
||||||
an_dateofbirth_$i_label.setAttribute('class', 'label');
|
|
||||||
an_dateofbirth_$i_label.innerHTML = 'Geburtsdatum';
|
|
||||||
an_dateofbirth_$i_label.appendChild(an_dateofbirth_$i);
|
|
||||||
|
|
||||||
let an_sex_$i = document.createElement('select');
|
|
||||||
let an_sex_F = document.createElement('option');
|
|
||||||
an_sex_F.value = 'F';
|
|
||||||
an_sex_F.innerHTML = "Weiblich";
|
|
||||||
an_sex_$i.appendChild(an_sex_F);
|
|
||||||
let an_sex_M = document.createElement('option');
|
|
||||||
an_sex_M.value = 'M';
|
|
||||||
an_sex_M.innerHTML = "Männlich";
|
|
||||||
an_sex_$i.appendChild(an_sex_M);
|
|
||||||
let an_sex_F_N = document.createElement('option');
|
|
||||||
an_sex_F_N.value = 'F_N';
|
|
||||||
an_sex_F_N.innerHTML = "Weiblich, kastriert";
|
|
||||||
an_sex_$i.appendChild(an_sex_F_N);
|
|
||||||
let an_sex_M_N = document.createElement('option');
|
|
||||||
an_sex_M_N.value = 'M_N';
|
|
||||||
an_sex_M_N.innerHTML = "Männlich, kastriert";
|
|
||||||
an_sex_$i.appendChild(an_sex_M_N);
|
|
||||||
let an_sex_I = document.createElement('option');
|
|
||||||
an_sex_I.value = 'I';
|
|
||||||
an_sex_I.innerHTML = "Intergeschlechtlich";
|
|
||||||
an_sex_$i.appendChild(an_sex_I);
|
|
||||||
an_sex_$i.setAttribute('name', 'sex');
|
|
||||||
an_sex_$i.setAttribute('class', 'input');
|
|
||||||
an_sex_$i.setAttribute('required', 'required');
|
|
||||||
let an_sex_$i_label = document.createElement('label');
|
|
||||||
an_sex_$i_label.setAttribute('class', 'label');
|
|
||||||
an_sex_$i_label.innerHTML = 'Geschlecht';
|
|
||||||
an_sex_$i_label.appendChild(an_sex_$i);
|
|
||||||
|
|
||||||
let an_description_$i = document.createElement('textarea');
|
|
||||||
an_description_$i.setAttribute('name', 'an_description');
|
|
||||||
an_description_$i.classList.add('input', 'textarea');
|
|
||||||
let an_description_$i_label = document.createElement('label');
|
|
||||||
an_description_$i_label.innerHTML = 'Beschreibung';
|
|
||||||
an_description_$i_label.classList.add('label');
|
|
||||||
an_description_$i_label.appendChild(an_description_$i);
|
|
||||||
|
|
||||||
an_fieldset_$i.appendChild(an_description_$i_label);
|
|
||||||
an_fieldset_$i.appendChild(an_name_$i_label);
|
|
||||||
an_fieldset_$i.appendChild(an_dateofbirth_$i_label);
|
|
||||||
an_fieldset_$i.appendChild(an_sex_$i_label);
|
|
||||||
an_fieldset_$i.appendChild(an_description_$i_label);
|
|
||||||
animals.appendChild(an_fieldset_$i);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
an_fieldset.appendChild(an_name_label);
|
|
||||||
an_fieldset.appendChild(an_location_string_label);
|
|
||||||
an_fieldset.appendChild(an_further_information_label);
|
|
||||||
an_fieldset.appendChild(an_species_label);
|
|
||||||
an_fieldset.appendChild(an_number_label);
|
|
||||||
an_fieldset.appendChild(an_dateofbirth_label);
|
|
||||||
an_fieldset.appendChild(an_sex_label);
|
|
||||||
an_fieldset.appendChild(an_searching_since_label);
|
|
||||||
|
|
||||||
let new_form = document.createElement('form');
|
|
||||||
new_form.classList.add('new-animal-ad', 'fixed-grid', 'has-3-cols', 'has-1-cols-mobile');
|
|
||||||
let div = document.createElement('div');
|
|
||||||
div.classList.add('grid');
|
|
||||||
let sButton = document.createElement('button');
|
|
||||||
sButton.classList.add('button');
|
|
||||||
sButton.innerHTML = "Abschicken";
|
|
||||||
|
|
||||||
div.appendChild(an_fieldset);
|
|
||||||
div.appendChild(animals);
|
|
||||||
div.appendChild(sButton);
|
|
||||||
new_form.appendChild(div);
|
|
||||||
document.querySelector('.main-content').appendChild(new_form);
|
|
||||||
|
|
||||||
// ------------------------------------------------ listeners
|
|
||||||
// number of animals
|
|
||||||
let tmpAnimal;
|
|
||||||
an_number.addEventListener('change', function () {
|
|
||||||
if (an_number.value > 0) {
|
|
||||||
hide(noteNumber);
|
|
||||||
} else {
|
|
||||||
show(noteNumber);
|
|
||||||
}
|
|
||||||
if (an_number.value < 2) {
|
|
||||||
hide(an_description_label);
|
|
||||||
hide(an_group_only_label);
|
|
||||||
an_group_only.selectedIndex = 1;
|
|
||||||
} else {
|
|
||||||
show(an_description_label);
|
|
||||||
show(an_group_only_label);
|
|
||||||
an_group_only.selectedIndex = 0;
|
|
||||||
}
|
|
||||||
for (let i = 0; i < an_max; i++) {
|
|
||||||
tmpAnimal = document.querySelector('.animal-' + i);
|
|
||||||
if (i < an_number.value) {
|
|
||||||
tmpAnimal.removeAttribute('disabled');
|
|
||||||
show(tmpAnimal);
|
|
||||||
} else {
|
|
||||||
tmpAnimal.setAttribute('disabled', 'true');
|
|
||||||
hide(tmpAnimal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// sex
|
|
||||||
an_sex.addEventListener('change', function () {
|
|
||||||
for (let i = 0; i < an_max; i++) {
|
|
||||||
let selList = document.querySelector('.animal-' + i).querySelector('[name="sex"]');
|
|
||||||
for (let j = 0; j < selList.options.length; j++) {
|
|
||||||
if (selList.options[j].value == an_sex.value) {
|
|
||||||
selList.selectedIndex = j;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// date of birth
|
|
||||||
an_dateofbirth.addEventListener('change', function () {
|
|
||||||
for (let i = 0; i < an_max; i++) {
|
|
||||||
document.querySelector('.animal-' + i).querySelector('[name="dateofbirth"]').value = an_dateofbirth.value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ------------------------------------------------ initialise
|
|
||||||
show(noteNumber);
|
|
||||||
hide(an_description_label);
|
|
||||||
hide(an_group_only_label);
|
|
||||||
for (let i = 0; i < an_max; i++) {
|
|
||||||
hide(document.querySelector('.animal-' + i));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------- submit
|
|
||||||
new_form.addEventListener('submit', function (event) {
|
|
||||||
event.preventDefault();
|
|
||||||
let date = new Date();
|
|
||||||
let postDate = date.toISOString().slice(0, 10);
|
|
||||||
const path = '';
|
|
||||||
|
|
||||||
let elResultsBd = document.createElement('div');
|
|
||||||
elResultsBd.classList.add('feedback-backdrop');
|
|
||||||
let elResults = document.createElement('div');
|
|
||||||
elResults.classList.add('feedback-add-new');
|
|
||||||
elResultsBd.appendChild(elResults);
|
|
||||||
document.querySelector('body').appendChild(elResultsBd);
|
|
||||||
|
|
||||||
let data = JSON.stringify({
|
|
||||||
"created_at": postDate,
|
|
||||||
"searching_since": an_searching_since.value,
|
|
||||||
"name": an_name.value,
|
|
||||||
"description": an_description.value,
|
|
||||||
"further_information": an_further_information.value,
|
|
||||||
"group_only": an_group_only.value,
|
|
||||||
"location_string": an_location_string.value,
|
|
||||||
});
|
|
||||||
|
|
||||||
async function submitAN() {
|
|
||||||
const csrftoken = getCookie('csrftoken');
|
|
||||||
let response = await fetch('http://localhost:8000/api/adoption_notice', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json;charset=utf-8',
|
|
||||||
'X-CSRFToken': csrftoken,
|
|
||||||
},
|
|
||||||
body: data,
|
|
||||||
});
|
|
||||||
console.log(response.status);
|
|
||||||
if (response.status === 201) {
|
|
||||||
let result = await response.json();
|
|
||||||
elResults.textContent = result.message + '<br>neue Id: ' + result.id;
|
|
||||||
elResults.classList.add('success');
|
|
||||||
} else {
|
|
||||||
elResults.textContent = 'Fehler! Status Code: ' + response.status;
|
|
||||||
elResults.classList.add('error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
submitAN();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -67,6 +67,51 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
$el.classList.remove("is-active");
|
$el.classList.remove("is-active");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MODALS //
|
||||||
|
|
||||||
|
function openModal($el) {
|
||||||
|
$el.classList.add('is-active');
|
||||||
|
send("Modal.open", {
|
||||||
|
modal: $el.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModal($el) {
|
||||||
|
$el.classList.remove('is-active');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeAllModals() {
|
||||||
|
(document.querySelectorAll('.modal') || []).forEach(($modal) => {
|
||||||
|
closeModal($modal);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a click event on buttons to open a specific modal
|
||||||
|
(document.querySelectorAll('.js-modal-trigger') || []).forEach(($trigger) => {
|
||||||
|
const modal = $trigger.dataset.target;
|
||||||
|
const $target = document.getElementById(modal);
|
||||||
|
|
||||||
|
$trigger.addEventListener('click', () => {
|
||||||
|
openModal($target);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add a click event on various child elements to close the parent modal
|
||||||
|
(document.querySelectorAll('.modal-background, .modal-close, .delete, .nf-modal-close') || []).forEach(($close) => {
|
||||||
|
const $target = $close.closest('.modal');
|
||||||
|
|
||||||
|
$close.addEventListener('click', () => {
|
||||||
|
closeModal($target);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add a keyboard event to close all modals
|
||||||
|
document.addEventListener('keydown', (event) => {
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
closeAllModals();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ def task_deactivate_unchecked():
|
|||||||
@celery_app.task(name="social_media.post_fedi")
|
@celery_app.task(name="social_media.post_fedi")
|
||||||
def task_post_to_fedi():
|
def task_post_to_fedi():
|
||||||
adoption_notice = SocialMediaPost.get_an_to_post()
|
adoption_notice = SocialMediaPost.get_an_to_post()
|
||||||
post_an_to_fedi(adoption_notice)
|
if adoption_notice is not None:
|
||||||
|
post_an_to_fedi(adoption_notice)
|
||||||
set_timestamp("task_social_media.post_fedi")
|
set_timestamp("task_social_media.post_fedi")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
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" %}
|
||||||
@@ -27,104 +27,106 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% include "fellchensammlung/partials/partial-adoption-notice-status.html" %}
|
{% include "fellchensammlung/partials/partial-adoption-notice-status.html" %}
|
||||||
<div class="columns">
|
<!--- Title level (including action dropdown) -->
|
||||||
<div class="column is-two-thirds">
|
<div class="block is-flex is-justify-content-space-between">
|
||||||
<!--- Title level (including action dropdown) -->
|
<div class="">
|
||||||
<div class="level">
|
<h2 class="title is-3 is-size-4-mobile">{{ adoption_notice.name }}</h2>
|
||||||
<div class="level-left">
|
</div>
|
||||||
<div class="level-item">
|
|
||||||
<p class="title is-3 is-size-4-mobile">{{ adoption_notice.name }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="level-right">
|
<div class="">
|
||||||
<div class="level-item">
|
<div class="dropdown is-right">
|
||||||
<div class="dropdown is-right">
|
<div class="dropdown-trigger">
|
||||||
<div class="dropdown-trigger">
|
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu4">
|
||||||
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu4">
|
<span><i class="fas fa-gear" aria-label="{% trans 'Aktionen' %}"></i></span>
|
||||||
<span><i class="fas fa-gear" aria-label="{% trans 'Aktionen' %}"></i></span>
|
<span class="icon is-small">
|
||||||
<span class="icon is-small">
|
|
||||||
<i class="fas fa-angle-down" aria-hidden="true"></i>
|
<i class="fas fa-angle-down" aria-hidden="true"></i>
|
||||||
</span>
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<!--- Action menu (dropdown) --->
|
||||||
|
<div class="dropdown-menu" role="menu">
|
||||||
|
<div class="dropdown-content">
|
||||||
|
{% if is_subscribed %}
|
||||||
|
<form class="dropdown-item" method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="action" value="unsubscribe">
|
||||||
|
|
||||||
|
<button type="submit" id="submit">
|
||||||
|
<i class="fas fa-bell-slash fa-fw"
|
||||||
|
aria-hidden="true"></i> {% trans 'Deabonnieren' %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
<!--- Action menu (dropdown) --->
|
|
||||||
<div class="dropdown-menu" role="menu">
|
|
||||||
<div class="dropdown-content">
|
|
||||||
{% if is_subscribed %}
|
|
||||||
<form class="dropdown-item" method="POST">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" name="action" value="unsubscribe">
|
|
||||||
|
|
||||||
<button type="submit" id="submit">
|
</form>
|
||||||
<i class="fas fa-bell-slash fa-fw"
|
{% else %}
|
||||||
aria-hidden="true"></i> {% trans 'Deabonnieren' %}
|
<form class="dropdown-item" method="POST">
|
||||||
</button>
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="action" value="subscribe">
|
||||||
|
|
||||||
</form>
|
<button type="submit" id="submit">
|
||||||
{% else %}
|
<i class="fas fa-bell fa-fw"
|
||||||
<form class="dropdown-item" method="POST">
|
aria-hidden="true"></i> {% trans 'Abonnieren' %}
|
||||||
{% csrf_token %}
|
</button>
|
||||||
<input type="hidden" name="action" value="subscribe">
|
|
||||||
|
|
||||||
<button type="submit" id="submit">
|
</form>
|
||||||
<i class="fas fa-bell fa-fw"
|
{% endif %}
|
||||||
aria-hidden="true"></i> {% trans 'Abonnieren' %}
|
<hr class="dropdown-divider">
|
||||||
</button>
|
|
||||||
|
|
||||||
</form>
|
{% if has_edit_permission %}
|
||||||
{% endif %}
|
<form class="dropdown-item" method="POST">
|
||||||
<hr class="dropdown-divider">
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="action" value="checked_active">
|
||||||
{% if has_edit_permission %}
|
<button type="submit" id="submit">
|
||||||
|
<i class="fas fa-check fa-fw"
|
||||||
<form class="dropdown-item" method="POST">
|
aria-hidden="true"></i> {% trans 'Als aktiv bestätigen' %}
|
||||||
{% csrf_token %}
|
</button>
|
||||||
<input type="hidden" name="action" value="checked_active">
|
</form>
|
||||||
<button type="submit" id="submit">
|
<a class="dropdown-item"
|
||||||
<i class="fas fa-check fa-fw"
|
href="{% url 'adoption-notice-edit' adoption_notice_id=adoption_notice.pk %}">
|
||||||
aria-hidden="true"></i> {% trans 'Als aktiv bestätigen' %}
|
<i class="fas fa-pencil fa-fw"
|
||||||
</button>
|
aria-hidden="true"></i> {% translate 'Bearbeiten' %}
|
||||||
</form>
|
</a>
|
||||||
<a class="dropdown-item"
|
<a class="dropdown-item"
|
||||||
href="{% url 'adoption-notice-edit' adoption_notice_id=adoption_notice.pk %}">
|
href="{% url 'adoption-notice-add-photo' adoption_notice.pk %}">
|
||||||
<i class="fas fa-pencil fa-fw"
|
<i class="fas fa-image fa-fw"
|
||||||
aria-hidden="true"></i> {% translate 'Bearbeiten' %}
|
aria-hidden="true"></i> {% trans 'Bilder hinzufügen' %}
|
||||||
</a>
|
</a>
|
||||||
<a class="dropdown-item"
|
<a class="dropdown-item"
|
||||||
href="{% url 'adoption-notice-add-photo' adoption_notice.pk %}">
|
href="{% url 'adoption-notice-add-animal' adoption_notice.pk %}">
|
||||||
<i class="fas fa-image fa-fw"
|
<i class="fas fa-plus fa-fw"
|
||||||
aria-hidden="true"></i> {% trans 'Bilder hinzufügen' %}
|
aria-hidden="true"></i> {% trans 'Tier hinzufügen' %}
|
||||||
</a>
|
</a>
|
||||||
<a class="dropdown-item"
|
<a class="dropdown-item"
|
||||||
href="{% url 'adoption-notice-add-animal' adoption_notice.pk %}">
|
href="{% url 'adoption-notice-close' adoption_notice_id=adoption_notice.pk %}">
|
||||||
<i class="fas fa-plus fa-fw"
|
<i class="fas fa-circle-xmark fa-fw"
|
||||||
aria-hidden="true"></i> {% trans 'Tier hinzufügen' %}
|
aria-hidden="true"></i> {% trans 'Deaktivieren' %}
|
||||||
</a>
|
</a>
|
||||||
<a class="dropdown-item"
|
<hr class="dropdown-divider">
|
||||||
href="{% url 'adoption-notice-close' adoption_notice_id=adoption_notice.pk %}">
|
{% endif %}
|
||||||
<i class="fas fa-circle-xmark fa-fw"
|
<a class="dropdown-item" href="{{ adoption_notice.get_report_url }}">
|
||||||
aria-hidden="true"></i> {% trans 'Deaktivieren' %}
|
<i class="fas fa-flag"
|
||||||
</a>
|
aria-hidden="true"></i> {% trans 'Melden' %}
|
||||||
<hr class="dropdown-divider">
|
</a>
|
||||||
{% endif %}
|
{% if request.user.is_superuser %}
|
||||||
<a class="dropdown-item" href="{{ adoption_notice.get_report_url }}">
|
{% if oxitraffic_base_url %}
|
||||||
<i class="fas fa-flag"
|
<hr class="dropdown-divider">
|
||||||
aria-hidden="true"></i> {% trans 'Melden' %}
|
<a class="dropdown-item"
|
||||||
</a>
|
href="{{ oxitraffic_base_url }}/stats?path={{ adoption_notice.get_absolute_url }}">
|
||||||
{% if request.user.is_superuser %}
|
<i class="fas fa-chart-line fa-fw"></i> {% trans 'Aufrufe' %}
|
||||||
<hr class="dropdown-divider">
|
</a>
|
||||||
<a class="dropdown-item is-warning"
|
{% endif %}
|
||||||
href="{% url adoption_notice_meta|admin_urlname:'change' adoption_notice.pk %}">
|
<hr class="dropdown-divider">
|
||||||
<i class="fa-solid fa-tools fa-fw"></i> Admin interface
|
<a class="dropdown-item is-warning"
|
||||||
</a>
|
href="{% url adoption_notice_meta|admin_urlname:'change' adoption_notice.pk %}">
|
||||||
{% endif %}
|
<i class="fa-solid fa-tools fa-fw"></i> Admin interface
|
||||||
</div>
|
</a>
|
||||||
</div>
|
{% endif %}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-two-thirds">
|
||||||
<!--- General Information --->
|
<!--- General Information --->
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="cell">
|
<div class="cell">
|
||||||
@@ -157,7 +159,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--- Images and Description --->
|
<!--- Images and Description --->
|
||||||
<div class="columns">
|
<div class="columns block">
|
||||||
<!--- Images --->
|
<!--- Images --->
|
||||||
{% if adoption_notice.get_photos %}
|
{% if adoption_notice.get_photos %}
|
||||||
<div class="column block">
|
<div class="column block">
|
||||||
@@ -218,13 +220,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="block">
|
||||||
|
{% include 'fellchensammlung/partials/adoption_process/base.html' %}
|
||||||
|
</div>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
{% if adoption_notice.further_information %}
|
{% if adoption_notice.further_information %}
|
||||||
<form method="get" action="{% url 'external-site' %}">
|
<form method="get" action="{% url 'external-site' %}">
|
||||||
<input type="hidden" name="url" value="{{ adoption_notice.further_information }}">
|
<input type="hidden" name="url" value="{{ adoption_notice.further_information }}">
|
||||||
<button class="button is-primary is-fullwidth" type="submit" id="submit">
|
<button class="button is-warning is-fullwidth" type="submit" id="submit">
|
||||||
{{ adoption_notice.further_information | domain }} <i
|
{% translate 'Weitere Informationen' %}: {{ adoption_notice.further_information | domain }}
|
||||||
class="fa-solid fa-arrow-up-right-from-square fa-fw"></i>
|
<i
|
||||||
|
class="fa-solid fa-arrow-up-right-from-square fa-fw"></i>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -238,7 +244,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
|
||||||
<div class="block">
|
<div class="block">
|
||||||
|
|||||||
@@ -19,53 +19,10 @@
|
|||||||
<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">
|
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
{% include "fellchensammlung/partials/partial-rescue-organization-contact.html" %}
|
{% include "fellchensammlung/partials/rescue_orgs/partial-rescue-organization-contact.html" %}
|
||||||
</div>
|
</div>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<a class="button is-warning is-fullwidth" href="{% url org_meta|admin_urlname:'change' org.pk %}">
|
<a class="button is-warning is-fullwidth" href="{% url org_meta|admin_urlname:'change' org.pk %}">
|
||||||
|
|||||||
@@ -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,69 +26,87 @@
|
|||||||
|
|
||||||
<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="">
|
|
||||||
<p>
|
|
||||||
<a class="button is-warning" href="{% url 'password_change' %}">{% translate "Change password" %}</a>
|
|
||||||
<a class="button is-info" href="{% url 'user-me-export' %}">{% translate "Daten exportieren" %}</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if user.id is request.user.id %}
|
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<h2 class="title is-2">{% trans 'Einstellungen' %}</h2>
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
<form class="block" action="" method="POST">
|
<div class="control">
|
||||||
{% csrf_token %}
|
<a class="button is-warning"
|
||||||
{% if user.email_notifications %}
|
href="{% url 'account_change_password' %}">{% translate "Passwort ändern" %}</a>
|
||||||
<label class="toggle">
|
|
||||||
<input type="submit" class="toggle-checkbox checked" name="toggle_email_notifications">
|
|
||||||
<div class="toggle-switch round "></div>
|
|
||||||
<span class="slider-label">
|
|
||||||
{% translate 'E-Mail Benachrichtigungen' %}
|
|
||||||
</span>
|
|
||||||
</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 %}
|
|
||||||
<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>
|
</div>
|
||||||
</details>
|
<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>
|
</div>
|
||||||
|
|
||||||
<h2 class="title is-2">{% translate 'Benachrichtigungen' %}</h2>
|
{% if user.id is request.user.id %}
|
||||||
{% include "fellchensammlung/lists/list-notifications.html" %}
|
<div class="block">
|
||||||
|
<h2 class="title is-2">{% trans 'Einstellungen' %}</h2>
|
||||||
|
<form class="block" action="" method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% if user.email_notifications %}
|
||||||
|
<label class="toggle">
|
||||||
|
<input type="submit" class="toggle-checkbox checked" name="toggle_email_notifications">
|
||||||
|
<div class="toggle-switch round "></div>
|
||||||
|
<span class="slider-label">
|
||||||
|
{% translate 'E-Mail Benachrichtigungen' %}
|
||||||
|
</span>
|
||||||
|
</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 %}
|
||||||
|
<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>
|
||||||
|
|
||||||
<h2 class="title is-2">{% translate 'Abonnierte Suchen' %}</h2>
|
</div>
|
||||||
{% include "fellchensammlung/lists/list-search-subscriptions.html" %}
|
|
||||||
|
|
||||||
<h2 class="title is-2">{% translate 'Meine Vermittlungen' %}</h2>
|
<h2 class="title is-2">{% translate 'Benachrichtigungen' %}</h2>
|
||||||
{% include "fellchensammlung/lists/list-adoption-notices.html" %}
|
{% include "fellchensammlung/lists/list-notifications.html" %}
|
||||||
|
|
||||||
{% endif %}
|
<h2 class="title is-2">{% translate 'Abonnierte Suchen' %}</h2>
|
||||||
|
{% include "fellchensammlung/lists/list-search-subscriptions.html" %}
|
||||||
|
|
||||||
|
<h2 class="title is-2">{% translate 'Meine Vermittlungen' %}</h2>
|
||||||
|
{% include "fellchensammlung/lists/list-adoption-notices.html" %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
{% load custom_tags %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
{% get_current_language as LANGUAGE_CODE %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="{{ LANGUAGE_CODE }}">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
{% block title %}{% endblock %}
|
||||||
|
{% block og_title %}{% endblock %}
|
||||||
|
{% block description %}{% endblock %}
|
||||||
|
{% block og_description %}{% endblock %}
|
||||||
|
{% block og_image %}{% endblock %}
|
||||||
|
<link rel="canonical" href="{% block canonical_url %}{% endblock %}">
|
||||||
|
|
||||||
|
<!-- Add additional CSS in static file -->
|
||||||
|
<link rel="stylesheet" href="{% static 'fellchensammlung/css/main.css' %}">
|
||||||
|
<link rel="stylesheet" href="{% static 'fellchensammlung/css/photoswipe.css' %}">
|
||||||
|
<link href="{% static 'fontawesomefree/css/fontawesome.css' %}" rel="stylesheet" type="text/css">
|
||||||
|
<link href="{% static 'fontawesomefree/css/brands.css' %}" rel="stylesheet" type="text/css">
|
||||||
|
<link href="{% static 'fontawesomefree/css/solid.css' %}" rel="stylesheet" type="text/css">
|
||||||
|
|
||||||
|
{% if background_color %}
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: #{{ background_color }};
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<script src="{% static 'fellchensammlung/js/custom.js' %}"></script>
|
||||||
|
<script src="{% static 'fellchensammlung/js/toggles.js' %}"></script>
|
||||||
|
<script src="{% static 'fellchensammlung/js/jquery.min.js' %}"></script>
|
||||||
|
<script type="module" src="{% static 'fellchensammlung/js/photoswipe.js' %}"></script>
|
||||||
|
{% block additional_scrips %}{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="{% static 'fellchensammlung/favicon/apple-touch-icon.png' %}">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="{% static 'fellchensammlung/favicon/favicon-32x32.png' %}">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="{% static 'fellchensammlung/favicon/favicon-16x16.png' %}">
|
||||||
|
{% get_oxitraffic_script_if_enabled %}
|
||||||
|
<base target="_parent"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="embed-main-content">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{% extends "fellchensammlung/embeddables/embedding-base.html" %}
|
||||||
|
{% load custom_tags %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="block">
|
||||||
|
{% include "fellchensammlung/lists/list-adoption-notices.html" %}
|
||||||
|
</div>
|
||||||
|
{% 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>
|
||||||
|
|||||||
@@ -34,6 +34,10 @@
|
|||||||
{% translate 'Das Notfellchen Projekt' %}
|
{% translate 'Das Notfellchen Projekt' %}
|
||||||
</a>
|
</a>
|
||||||
<br/>
|
<br/>
|
||||||
|
<a href="{% url "contact" %}">
|
||||||
|
{% translate 'Kontakt' %}
|
||||||
|
</a>
|
||||||
|
<br/>
|
||||||
<a href="{% url "buying" %}">
|
<a href="{% url "buying" %}">
|
||||||
{% translate 'Ratten kaufen' %}
|
{% translate 'Ratten kaufen' %}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -81,6 +81,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="an-organization">
|
||||||
|
{{ form.organization.label }}
|
||||||
|
{% if form.organization.field.required %}<span class="special_class">*</span>{% endif %}
|
||||||
|
</label>
|
||||||
|
<div class="select">
|
||||||
|
{{ form.organization|attr:"id:an-organization" }}
|
||||||
|
</div>
|
||||||
|
<div class="help">
|
||||||
|
{{ form.organization.help_text }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="notification is-info">
|
<div class="notification is-info">
|
||||||
<p>
|
<p>
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,13 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load widget_tweaks %}
|
||||||
|
|
||||||
<div class="card">
|
<form method="POST">
|
||||||
<div class="card-header">
|
{% csrf_token %}
|
||||||
<div class="card-header-title">
|
<input type="hidden" name="action" value="comment">
|
||||||
{% blocktrans %}
|
<div class="field">
|
||||||
Als {{ user }} kommentieren
|
{{ comment_form.text |add_class:"input textarea"|attr:"rows:3"|attr:"placeholder:Neuer Kommentar" }}
|
||||||
{% endblocktrans %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-content">
|
<div class="control">
|
||||||
<form method="POST">
|
<input type="submit" class="button is-primary" value="{% trans 'Kommentieren' %}">
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" name="action" value="comment">
|
|
||||||
{{ comment_form }}
|
|
||||||
<input type="submit" class="button is-primary" value="{% trans 'Kommentieren' %}">
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
@@ -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 %}
|
||||||
@@ -4,26 +4,84 @@
|
|||||||
{% load widget_tweaks %}
|
{% load widget_tweaks %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div>
|
<div class="block">
|
||||||
{% blocktranslate %}
|
<p>
|
||||||
Lade hier ein Foto hoch - wähle den Titel wie du willst und mach bitte eine Bildbeschreibung,
|
{% blocktranslate %}
|
||||||
damit die Fotos auch für blinde und sehbehinderte Personen zugänglich sind.
|
Lade hier ein Foto hoch. Füge bitte eine Bildbeschreibung hinzu,
|
||||||
{% endblocktranslate %}
|
damit die Fotos auch für blinde und sehbehinderte Personen zugänglich sind.
|
||||||
<p><a class="button" target="_blank"
|
{% endblocktranslate %}
|
||||||
href="https://www.dbsv.org/bildbeschreibung-4-regeln.html">{% translate 'Anleitung für Bildbeschreibungen' %}</a>
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<form method="post" enctype="multipart/form-data">
|
<div class="block">
|
||||||
{% csrf_token %}
|
<form method="post" enctype="multipart/form-data">
|
||||||
<div class="field">
|
{% csrf_token %}
|
||||||
<label class="label" for="image">{{ form.image.label }}</label>
|
<div class="file has-name is-boxed is-primary is-">
|
||||||
{{ form.image|add_class:"input"|attr:"id:image" }}
|
<label class="file-label" for="image">
|
||||||
</div>
|
{{ form.image|add_class:"file-input"|attr:"id:image" }}
|
||||||
<div class="field">
|
<span class="file-cta">
|
||||||
<label class="label" for="alt-text">{{ form.alt_text.label }}</label>
|
<span class="file-icon">
|
||||||
{{ form.alt_text|add_class:"textarea"|attr:"id:alt-text"|attr:"rows:3" }}
|
<i class="fas fa-upload"></i>
|
||||||
<div class="is-danger">{{ form.alt_text.errors }}</div>
|
</span>
|
||||||
</div>
|
<span class="file-label">{{ form.image.help_text }}</span>
|
||||||
<input class="button is-primary" type="submit" value="{% translate "Speichern" %}">
|
</span>
|
||||||
</form>
|
<span class="file-name" id="image-upload-filename">...</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--- Image Preview: Only shown if image gets selected --->
|
||||||
|
<div class="field" id="image-preview-wrapper" style="display: none;">
|
||||||
|
<label class="label">{% translate "Vorschau" %}</label>
|
||||||
|
<div class="box has-text-centered">
|
||||||
|
<figure class="image is-256x256 is-inline-block">
|
||||||
|
<img id="image-preview" src="" alt="{% translate 'Bildvorschau' %}" class="is-rounded">
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="alt-text">{{ form.alt_text.label }}</label>
|
||||||
|
{{ form.alt_text|add_class:"textarea"|attr:"id:alt-text"|attr:"rows:3" }}
|
||||||
|
<div class="help">
|
||||||
|
{{ form.alt_text.help_text }}
|
||||||
|
</div>
|
||||||
|
<div class="is-danger">{{ form.alt_text.errors }}</div>
|
||||||
|
</div>
|
||||||
|
<input class="button is-primary" type="submit" value="{% translate "Speichern" %}">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="block">
|
||||||
|
<a class="button is-info is-warning is-fullwidth" target="_blank"
|
||||||
|
href="https://www.dbsv.org/bildbeschreibung-4-regeln.html">
|
||||||
|
<i class="fa-solid fa-link fa-fw"></i> {% translate 'Anleitung für Bildbeschreibungen' %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const input = document.getElementById("image");
|
||||||
|
const previewWrapper = document.getElementById("image-preview-wrapper");
|
||||||
|
const preview = document.getElementById("image-preview");
|
||||||
|
const filename = document.getElementById("image-upload-filename");
|
||||||
|
|
||||||
|
input.addEventListener("change", function () {
|
||||||
|
const file = this.files[0];
|
||||||
|
if (file) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = function (e) {
|
||||||
|
preview.src = e.target.result;
|
||||||
|
previewWrapper.style.display = "block";
|
||||||
|
filename.innerText = file.name;
|
||||||
|
}
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
} else {
|
||||||
|
preview.src = "";
|
||||||
|
previewWrapper.style.display = "none";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% 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>
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="264.58334mm"
|
||||||
|
height="264.58334mm"
|
||||||
|
viewBox="0 0 264.58334 264.58334"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
xml:space="preserve"
|
||||||
|
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||||
|
sodipodi:docname="drawing.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:zoom="0.51136354"
|
||||||
|
inkscape:cx="1504.8003"
|
||||||
|
inkscape:cy="472.26675"
|
||||||
|
inkscape:window-width="2048"
|
||||||
|
inkscape:window-height="1208"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1">
|
||||||
|
<inkscape:page
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
width="264.58334"
|
||||||
|
height="264.58334"
|
||||||
|
id="page1"
|
||||||
|
margin="0"
|
||||||
|
bleed="0"/>
|
||||||
|
<inkscape:page
|
||||||
|
x="274.58334"
|
||||||
|
y="0"
|
||||||
|
width="264.58334"
|
||||||
|
height="264.58334"
|
||||||
|
id="page2"
|
||||||
|
margin="0"
|
||||||
|
bleed="0"/>
|
||||||
|
<inkscape:page
|
||||||
|
x="549.16669"
|
||||||
|
y="0"
|
||||||
|
width="264.58334"
|
||||||
|
height="264.58334"
|
||||||
|
id="page4"
|
||||||
|
margin="0"
|
||||||
|
bleed="0"/>
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs id="defs1"/>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<rect
|
||||||
|
style="fill:#6cd4ff;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;fill-opacity:0.73333335"
|
||||||
|
id="rect1"
|
||||||
|
width="264.58337"
|
||||||
|
height="264.58337"
|
||||||
|
x="0"
|
||||||
|
y="0"/>
|
||||||
|
<rect
|
||||||
|
style="fill:#6cd4ff;fill-opacity:0.733333;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"
|
||||||
|
id="rect1-2"
|
||||||
|
width="264.58337"
|
||||||
|
height="264.58337"
|
||||||
|
x="274.58334"
|
||||||
|
y="0"/>
|
||||||
|
<rect
|
||||||
|
style="fill:#6cd4ff;fill-opacity:0.733333;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"
|
||||||
|
id="rect1-0"
|
||||||
|
width="264.58337"
|
||||||
|
height="264.58337"
|
||||||
|
x="549.16669"
|
||||||
|
y="0"/>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-size:19.7556px;line-height:1.3;font-family:'Latin Modern Mono';-inkscape-font-specification:'Latin Modern Mono, Normal';text-align:start;letter-spacing:0px;word-spacing:-0.574146px;writing-mode:lr-tb;direction:ltr;text-anchor:start;white-space:pre;inline-size:230.276;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"
|
||||||
|
x="16.116602"
|
||||||
|
y="81.350471"
|
||||||
|
id="text4"
|
||||||
|
transform="translate(-7.9696277,63.01184)"><tspan
|
||||||
|
x="16.116602"
|
||||||
|
y="81.350471"
|
||||||
|
id="tspan2">{{ adoption_notice.short_description }}</tspan></text>
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:3.17108;stroke-linecap:round;stroke-linejoin:round"
|
||||||
|
id="rect4"
|
||||||
|
width="192.30475"
|
||||||
|
height="93.450798"
|
||||||
|
x="42.798157"
|
||||||
|
y="14.846257"/>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-size:19.7556px;line-height:1.1;font-family:'Latin Modern Mono';-inkscape-font-specification:'Latin Modern Mono, Normal';text-align:start;letter-spacing:0px;word-spacing:-0.79286px;writing-mode:lr-tb;direction:ltr;text-anchor:start;white-space:pre;inline-size:192.796;fill:#000000;fill-opacity:0.733333;stroke:none;stroke-width:2.76188;stroke-linecap:round;stroke-linejoin:round"
|
||||||
|
x="98.301682"
|
||||||
|
y="49.274101"
|
||||||
|
id="text1"
|
||||||
|
transform="translate(-58.467048,-14.731528)"><tspan
|
||||||
|
x="98.301682"
|
||||||
|
y="49.274101"
|
||||||
|
id="tspan4"><tspan
|
||||||
|
dx="0 0.79285997 -0.79286003 0 0 0 0 0 0.79285902 -0.79285526 0 0.79285902"
|
||||||
|
style="text-align:center;text-anchor:middle"
|
||||||
|
id="tspan3">{{ adoption_notice.name }}</tspan>
|
||||||
|
</tspan>
|
||||||
|
</text>
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"
|
||||||
|
id="rect4-8"
|
||||||
|
width="175.74771"
|
||||||
|
height="40.675236"
|
||||||
|
x="309.5625"
|
||||||
|
y="17.4625"/>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-size:36.6612px;line-height:1.3;font-family:'Latin Modern Mono';-inkscape-font-specification:'Latin Modern Mono, Normal';text-align:start;letter-spacing:0px;word-spacing:-1.06546px;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#000000;fill-opacity:0.733333;stroke:none;stroke-width:3.71147;stroke-linecap:round;stroke-linejoin:round"
|
||||||
|
x="328.02808"
|
||||||
|
y="47.813782"
|
||||||
|
id="text1-7">
|
||||||
|
<tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan1-9"
|
||||||
|
style="fill:#000000;fill-opacity:0.733333;stroke-width:3.71147"
|
||||||
|
x="328.02808"
|
||||||
|
y="47.813782">
|
||||||
|
Ratte 1
|
||||||
|
</tspan>
|
||||||
|
</text>
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"
|
||||||
|
id="rect4-7"
|
||||||
|
width="175.74771"
|
||||||
|
height="40.675236"
|
||||||
|
x="597.69373"
|
||||||
|
y="18.520834"/>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-size:36.6612px;line-height:1.3;font-family:'Latin Modern Mono';-inkscape-font-specification:'Latin Modern Mono, Normal';text-align:start;letter-spacing:0px;word-spacing:-1.06546px;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#000000;fill-opacity:0.733333;stroke:none;stroke-width:3.71147;stroke-linecap:round;stroke-linejoin:round"
|
||||||
|
x="616.1593"
|
||||||
|
y="48.872116"
|
||||||
|
id="text1-5">
|
||||||
|
<tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan1-92"
|
||||||
|
style="fill:#000000;fill-opacity:0.733333;stroke-width:3.71147"
|
||||||
|
x="616.1593"
|
||||||
|
y="48.872116">Ratte 2</tspan>
|
||||||
|
</text>
|
||||||
|
<image
|
||||||
|
width="116.45744"
|
||||||
|
height="145.62663"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
xlink:href="data:image/jpeg;base64,{{ adoption_notice.get_photo.as_base64 }};"
|
||||||
|
id="image1"
|
||||||
|
x="339.55661"
|
||||||
|
y="87.612373"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 7.3 KiB |
@@ -3,10 +3,23 @@
|
|||||||
<div class="grid is-col-min-10">
|
<div class="grid is-col-min-10">
|
||||||
{% for adoption_notice in adoption_notices %}
|
{% for adoption_notice in adoption_notices %}
|
||||||
<div class="cell">
|
<div class="cell">
|
||||||
{% include "fellchensammlung/partials/partial-adoption-notice-minimal.html" %}
|
{% if expand %}
|
||||||
|
{% include "fellchensammlung/partials/partial-adoption-notice.html" %}
|
||||||
|
{% else %}
|
||||||
|
{% include "fellchensammlung/partials/partial-adoption-notice-minimal.html" %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>{% translate "Keine Vermittlungen gefunden." %}</p>
|
<article class="message is-warning">
|
||||||
|
<div class="message-header">
|
||||||
|
<p>{% translate "Keine Vermittlungen gefunden." %}</p>
|
||||||
|
</div>
|
||||||
|
<div class="message-body">
|
||||||
|
{% blocktranslate %}
|
||||||
|
Versuche es zu einem späteren Zeitpunkt erneut.
|
||||||
|
{% endblocktranslate %}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -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 %}
|
||||||
|
|||||||
@@ -11,12 +11,12 @@
|
|||||||
<div class="control">
|
<div class="control">
|
||||||
<div class="select">
|
<div class="select">
|
||||||
<select id="reason_for_closing" name="reason_for_closing">
|
<select id="reason_for_closing" name="reason_for_closing">
|
||||||
<option value="successful_with_notfellchen">{% translate 'Vermittelt mit Hilfe von Notfellchen' %}</option>
|
<option value="closed_successful_with_notfellchen">{% translate 'Vermittelt mit Hilfe von Notfellchen' %}</option>
|
||||||
<option value="successful_without_notfellchen">{% translate 'Vermittelt ohne 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_for_other_adoption_notice">{% translate 'Vermittlung zugunsten einer anderen geschlossen' %}</option>
|
||||||
<option value="not_open_for_adoption_anymore">{% translate 'Nicht mehr zu vermitteln (z.B. aufgrund von Krankheit)' %}</option>
|
<option value="closed_not_open_for_adoption_anymore">{% translate 'Nicht mehr zu vermitteln (z.B. aufgrund von Krankheit)' %}</option>
|
||||||
<option value="animal_died">{% translate 'Tod des Tiers' %}</option>
|
<option value="closed_animal_died">{% translate 'Tod des Tiers' %}</option>
|
||||||
<option value="other">{% translate 'Anderer Grund' %}</option>
|
<option value="closed_other">{% translate 'Anderer Grund' %}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{% load custom_tags %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2 class="card-header-title">
|
||||||
|
{% translate 'Adoptionsprozess' %}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-content">
|
||||||
|
{% include adoption_process_template %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
{% translate 'Kontaktiere die Person mit den Kontaktdaten in der Beschreibung.' %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<ol class="adoption-process">
|
||||||
|
{% if adoption_notice.organization %}
|
||||||
|
{% if adoption_notice.further_information %}
|
||||||
|
<li>{% translate 'Link zu "Weiteren Informationen" prüfen' %}</li>
|
||||||
|
{% endif %}
|
||||||
|
<li>
|
||||||
|
{% translate 'Organization kontaktieren' %}<br>
|
||||||
|
<a class="button is-info is-small is-fullwidth"
|
||||||
|
href="{{ adoption_notice.organization.get_absolute_url }}">
|
||||||
|
{% translate 'Kontaktdaten' %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>{% translate 'Bei erfolgreicher Vermittlung: Vermittlung als geschlossen melden' %}</li>
|
||||||
|
{% else %}
|
||||||
|
{% if adoption_notice.further_information %}
|
||||||
|
<li>{% translate 'Link zu "Weiteren Informationen" prüfen' %}</li>
|
||||||
|
<li>{% translate 'Person, die die Vermittlung eingestellt hat, kontaktieren' %}</li>
|
||||||
|
{% else %}
|
||||||
|
<li>
|
||||||
|
{% translate 'Person, die die Vermittlung eingestellt hat, per Kommentar kontaktieren' %}
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
<li>{% translate 'Bei erfolgreicher Vermittlung: Vermittlung als geschlossen melden' %}</li>
|
||||||
|
{% endif %}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
<div id="modal-shortcuts" class="modal">
|
||||||
|
<div class="modal-background"></div>
|
||||||
|
<div class="modal-card">
|
||||||
|
<header class="modal-card-head">
|
||||||
|
<p class="modal-card-title">{% trans 'Shortcuts' %}</p>
|
||||||
|
<button class="delete" aria-label="{% trans 'Schließen' %}"></button>
|
||||||
|
</header>
|
||||||
|
<section class="modal-card-body">
|
||||||
|
<div class="content">
|
||||||
|
<ul>
|
||||||
|
<li>{% trans 'Website öffnen' %}: <code>O</code></li>
|
||||||
|
<li>{% trans 'Website schließen' %}: <code>{% trans 'STRG' %} + W</code></li>
|
||||||
|
<li>{% trans 'Organisation als geprüft markieren' %}: <code>{% trans 'STRG' %} + W</code></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<footer class="modal-card-foot">
|
||||||
|
<div class="buttons">
|
||||||
|
<button class="button is-success nf-modal-close">{% translate 'Verstanden' %}</button>
|
||||||
|
<button class="button">{% translate 'Details' %}</button>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<button class="button is-info floating js-modal-trigger" data-target="modal-shortcuts">
|
||||||
|
<i class="fa-solid fa-keyboard fa-fw"></i> {% trans 'Shortcuts' %}
|
||||||
|
</button>
|
||||||
@@ -54,20 +54,27 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
{% if adoption_notice.get_photos|length > 1 %}
|
||||||
<div class="thumbnail-row minimal">
|
<div class="thumbnail-row minimal">
|
||||||
{% for photo in adoption_notice.get_photos|slice:"1:4" %}
|
{% for photo in adoption_notice.get_photos|slice:"1:4" %}
|
||||||
<div class="thumbnail">
|
<div class="thumbnail">
|
||||||
<a href="{{ MEDIA_URL }}{{ photo.image }}"
|
<a href="{{ MEDIA_URL }}{{ photo.image }}"
|
||||||
data-pswp-width="{{ photo.image.width }}"
|
data-pswp-width="{{ photo.image.width }}"
|
||||||
data-pswp-height="{{ photo.image.height }}"
|
data-pswp-height="{{ photo.image.height }}"
|
||||||
target="_blank">
|
target="_blank">
|
||||||
<img src="{{ MEDIA_URL }}{{ photo.image }}"
|
<img src="{{ MEDIA_URL }}{{ photo.image }}"
|
||||||
alt="{{ photo.alt_text }}">
|
alt="{{ photo.alt_text }}">
|
||||||
</a>
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{% if adoption_notice.description_100_short %}
|
||||||
|
<div class="content">
|
||||||
|
{{ adoption_notice.description_100_short | render_markdown }}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endif %}
|
||||||
</div>
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if adoption_notice.description_short %}
|
{% if adoption_notice.description_short %}
|
||||||
|
|||||||
@@ -4,14 +4,10 @@
|
|||||||
{% if adoption_notice.is_closed %}
|
{% if adoption_notice.is_closed %}
|
||||||
<article class="message is-warning">
|
<article class="message is-warning">
|
||||||
<div class="message-header">
|
<div class="message-header">
|
||||||
<p>{% translate 'Vermittlung deaktiviert' %}</p>
|
<p>{% translate 'Vermittlung geschlossen' %}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="message-body content">
|
<div class="message-body content">
|
||||||
{% blocktranslate %}
|
{{ adoption_notice.status_description }}
|
||||||
Diese Vermittlung wurde deaktiviert. Typischerweise passiert das, wenn die Tiere erfolgreich
|
|
||||||
vermittelt wurden.
|
|
||||||
In den Kommentaren findest du ggf. mehr Informationen.
|
|
||||||
{% endblocktranslate %}
|
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
{% elif adoption_notice.is_interested %}
|
{% elif adoption_notice.is_interested %}
|
||||||
@@ -29,14 +25,10 @@
|
|||||||
{% elif adoption_notice.is_awaiting_action %}
|
{% elif adoption_notice.is_awaiting_action %}
|
||||||
<article class="message is-warning">
|
<article class="message is-warning">
|
||||||
<div class="message-header">
|
<div class="message-header">
|
||||||
<p>{% translate 'Warten auf Aktivierung' %}</p>
|
<p>{% translate 'Wartet auf Freigabe von Moderator*innen' %}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="message-body content">
|
<div class="message-body content">
|
||||||
{% blocktranslate %}
|
{{ adoption_notice.status_description }}
|
||||||
Diese Vermittlung muss noch durch Moderator*innen aktiviert werden und taucht daher nicht auf der
|
|
||||||
Startseite auf.
|
|
||||||
Ggf. fehlen noch relevante Informationen.
|
|
||||||
{% endblocktranslate %}
|
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -1,40 +1,84 @@
|
|||||||
{% load custom_tags %}
|
{% load custom_tags %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
<div class="card an-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2 class="card-header-title">
|
||||||
|
<a href="{{ adoption_notice.get_absolute_url }}"> {{ adoption_notice.name }}</a>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="grid">
|
||||||
|
<div class="cell">
|
||||||
|
<!--- General Information --->
|
||||||
|
<div class="grid">
|
||||||
|
|
||||||
<div class="card">
|
<div class="cell">
|
||||||
<div>
|
|
||||||
<div class="header-card-adoption-notice">
|
{% if adoption_notice.organization %}
|
||||||
<h1><a class="heading-card-adoption-notice"
|
<div class="cell">
|
||||||
href="{{ adoption_notice.get_absolute_url }}"> {{ adoption_notice.name }}</a></h1>
|
<span>
|
||||||
<a class="adoption-card-report-link" href="{{ adoption_notice.get_report_url }}"><i
|
<i class="fa-solid fa-building fa-fw" aria-label="{% trans 'Tierschutzorganisation' %}"></i>
|
||||||
class="fa-solid fa-flag"></i></a>
|
<a href="{{ adoption_notice.organization.get_absolute_url }}"> {{ adoption_notice.organization }}</a>
|
||||||
</div>
|
</span>
|
||||||
<p><b>{% translate "Notfellchen" %}:</b> {{ adoption_notice.animals.all|join_link:", " | safe }}
|
|
||||||
</p>
|
</div>
|
||||||
<p>
|
{% else %}
|
||||||
<b>Ort</b>
|
<p>
|
||||||
{% if adoption_notice.location %}
|
<i class="fa-solid fa-location-dot fa-fw"></i>
|
||||||
{{ adoption_notice.location.str }}
|
{% if adoption_notice.location %}
|
||||||
{% else %}
|
{{ adoption_notice.location }}
|
||||||
{{ adoption_notice.location_string }}
|
{% else %}
|
||||||
{% endif %}
|
{{ adoption_notice.location_string }}
|
||||||
</p>
|
{% endif %}
|
||||||
<div class="content">
|
</p>
|
||||||
{% if adoption_notice.description %}
|
{% endif %}
|
||||||
{{ adoption_notice.description | render_markdown }}
|
</div>
|
||||||
{% else %}
|
<div class="cell">
|
||||||
<p>
|
{% include "fellchensammlung/partials/sex-overview.html" %}
|
||||||
{% translate "Keine Beschreibung" %}
|
</div>
|
||||||
</p>
|
</div>
|
||||||
{% endif %}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if adoption_notice.get_photo %}
|
|
||||||
<div class="adoption-notice-img">
|
{% if adoption_notice.get_photos %}
|
||||||
<img src="{{ MEDIA_URL }}/{{ adoption_notice.get_photo.image }}"
|
<!--- Images -->
|
||||||
alt="{{ adoption_notice.get_photo.alt_text }}">
|
<div class="gallery block">
|
||||||
|
{% with photo=adoption_notice.get_photos.0 %}
|
||||||
|
<div class="main-photo minimal">
|
||||||
|
<a href="{{ MEDIA_URL }}{{ photo.image }}"
|
||||||
|
data-pswp-width="{{ photo.image.width }}"
|
||||||
|
data-pswp-height="{{ photo.image.height }}"
|
||||||
|
target="_blank">
|
||||||
|
<img src="{{ MEDIA_URL }}{{ photo.image }}"
|
||||||
|
alt="{{ photo.alt_text }}">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
|
{% if adoption_notice.get_photos|length > 1 %}
|
||||||
|
<div class="thumbnail-row minimal">
|
||||||
|
{% for photo in adoption_notice.get_photos|slice:"1:4" %}
|
||||||
|
<div class="thumbnail">
|
||||||
|
<a href="{{ MEDIA_URL }}{{ photo.image }}"
|
||||||
|
data-pswp-width="{{ photo.image.width }}"
|
||||||
|
data-pswp-height="{{ photo.image.height }}"
|
||||||
|
target="_blank">
|
||||||
|
<img src="{{ MEDIA_URL }}{{ photo.image }}"
|
||||||
|
alt="{{ photo.alt_text }}">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
<!--- Description -->
|
||||||
|
{% if adoption_notice.description_short %}
|
||||||
|
<div class="content block">
|
||||||
|
{{ adoption_notice.description_short | render_markdown }}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
|
||||||
{% translate "Keine Foto" %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,14 +12,13 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<p class="is-italic">{% translate 'Noch keine Kommentare' %}</p>
|
<p class="is-italic">{% translate 'Noch keine Kommentare' %}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
<hr>
|
||||||
|
{% include "fellchensammlung/forms/form-comment.html" %}
|
||||||
|
{% else %}
|
||||||
|
<div class="card-footer-item">
|
||||||
|
{% translate 'Du musst dich einloggen um Kommentare zu hinterlassen' %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="block">
|
|
||||||
{% if user.is_authenticated %}
|
|
||||||
{% include "fellchensammlung/forms/form-comment.html" %}
|
|
||||||
{% else %}
|
|
||||||
<p class="card-footer-item">
|
|
||||||
{% translate 'Du musst dich einloggen um Kommentare zu hinterlassen' %}
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% if org.website %}
|
||||||
|
<a class="panel-block is-active" href="{{ org.website }}" target="_blank">
|
||||||
|
<span class="panel-icon">
|
||||||
|
<i class="fas fa-globe" aria-label="{% translate "Website" %}"></i>
|
||||||
|
</span>
|
||||||
|
{{ org.website }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if org.phone_number %}
|
||||||
|
<a class="panel-block is-active" href="tel:{{ org.phone_number }}">
|
||||||
|
<span class="panel-icon">
|
||||||
|
<i class="fas fa-phone" aria-label="{% translate "Telefonnummer" %}"></i>
|
||||||
|
</span>
|
||||||
|
{{ org.phone_number }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if org.email %}
|
||||||
|
<a class="panel-block is-active" href="mailto:{{ org.email }}">
|
||||||
|
<span class="panel-icon">
|
||||||
|
<i class="fas fa-envelope" aria-label="{% translate "E-Mail" %}"></i>
|
||||||
|
</span>
|
||||||
|
{{ org.email }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if org.fediverse_profile %}
|
||||||
|
<a class="panel-block is-active" href="{{ org.fediverse_profile }}" target="_blank">
|
||||||
|
<span class="panel-icon">
|
||||||
|
<i class="fas fa-hashtag" aria-label="{% translate "Fediverse Profil" %}"></i>
|
||||||
|
</span>
|
||||||
|
{{ org.fediverse_profile }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if org.instagram %}
|
||||||
|
<a class="panel-block is-active" href="{{ org.instagram }}" target="_blank">
|
||||||
|
<span class="panel-icon">
|
||||||
|
<i class="fa-brands fa-instagram" aria-label="{% translate "Instagram Profil" %}"></i>
|
||||||
|
</span>
|
||||||
|
{{ org.instagram }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if org.facebook %}
|
||||||
|
<a class="panel-block is-active" href="{{ org.facebook }}" target="_blank">
|
||||||
|
<span class="panel-icon">
|
||||||
|
<i class="fa-brands fa-facebook" aria-label="{% translate "Facebook Profil" %}"></i>
|
||||||
|
</span>
|
||||||
|
{{ org.facebook }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
{% load custom_tags %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
<div class="panel block">
|
|
||||||
<p class="panel-heading">{% trans "Kontaktdaten" %}</p>
|
|
||||||
{% if org.has_contact_data %}
|
|
||||||
{% if org.website %}
|
|
||||||
<a class="panel-block is-active" href="{{ org.website }}" target="_blank">
|
|
||||||
<span class="panel-icon">
|
|
||||||
<i class="fas fa-globe" aria-label="{% translate "Website" %}"></i>
|
|
||||||
</span>
|
|
||||||
{{ org.website }}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if org.phone_number %}
|
|
||||||
<a class="panel-block is-active" href="tel:{{ org.phone_number }}">
|
|
||||||
<span class="panel-icon">
|
|
||||||
<i class="fas fa-phone" aria-label="{% translate "Telefonnummer" %}"></i>
|
|
||||||
</span>
|
|
||||||
{{ org.phone_number }}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if org.email %}
|
|
||||||
<a class="panel-block is-active" href="mailto:{{ org.email }}">
|
|
||||||
<span class="panel-icon">
|
|
||||||
<i class="fas fa-envelope" aria-label="{% translate "E-Mail" %}"></i>
|
|
||||||
</span>
|
|
||||||
{{ org.email }}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if org.fediverse_profile %}
|
|
||||||
<a class="panel-block is-active" href="{{ org.fediverse_profile }}" target="_blank">
|
|
||||||
<span class="panel-icon">
|
|
||||||
<i class="fas fa-hashtag" aria-label="{% translate "Fediverse Profil" %}"></i>
|
|
||||||
</span>
|
|
||||||
{{ org.fediverse_profile }}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if org.instagram %}
|
|
||||||
<a class="panel-block is-active" href="{{ org.instagram }}" target="_blank">
|
|
||||||
<span class="panel-icon">
|
|
||||||
<i class="fa-brands fa-instagram" aria-label="{% translate "Instagram Profil" %}"></i>
|
|
||||||
</span>
|
|
||||||
{{ org.instagram }}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if org.facebook %}
|
|
||||||
<a class="panel-block is-active" href="{{ org.facebook }}" target="_blank">
|
|
||||||
<span class="panel-icon">
|
|
||||||
<i class="fa-brands fa-facebook" aria-label="{% translate "Facebook Profil" %}"></i>
|
|
||||||
</span>
|
|
||||||
{{ org.facebook }}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
<div class="panel-block is-active">
|
|
||||||
<span class="panel-icon">
|
|
||||||
<i class="fas fa-x" aria-label="{% translate "E-Mail" %}"></i>
|
|
||||||
</span>
|
|
||||||
{% translate 'Keine Kontaktdaten hinterlegt' %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</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>
|
||||||
@@ -75,15 +75,7 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer-item is-danger">
|
<div class="card-footer-item is-danger">
|
||||||
<form method="post">
|
<a href="{% url 'rescue-organization-exclude' rescue_organization_id=rescue_org.pk %}">{% translate "Von Check exkludieren" %}</a>
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden"
|
|
||||||
name="rescue_organization_id"
|
|
||||||
value="{{ rescue_org.pk }}">
|
|
||||||
<input type="hidden" name="action" value="exclude">
|
|
||||||
<button class="" type="submit">{% translate "Von Check exkludieren" %}</button>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{% load custom_tags %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<div class="panel block">
|
||||||
|
<p class="panel-heading">{% trans "Kontaktdaten" %}</p>
|
||||||
|
{% if org.has_contact_data %}
|
||||||
|
{% include "fellchensammlung/partials/partial-in-panel-contact-data.html" %}
|
||||||
|
{% elif org.parent_org.has_contact_data %}
|
||||||
|
{% with org=org.parent_org %}
|
||||||
|
{% include "fellchensammlung/partials/partial-in-panel-contact-data.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
{% else %}
|
||||||
|
<div class="panel-block is-active">
|
||||||
|
<span class="panel-icon">
|
||||||
|
<i class="fas fa-x" aria-label="{% translate "E-Mail" %}"></i>
|
||||||
|
</span>
|
||||||
|
{% translate 'Keine Kontaktdaten hinterlegt' %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
@@ -3,8 +3,10 @@
|
|||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h2 class="card-header-title"><a
|
<h2 class="card-header-title">
|
||||||
href="{{ rescue_organization.get_absolute_url }}"> {{ rescue_organization.name }}</a></h2>
|
<a href="{{ rescue_organization.get_absolute_url }}"> {{ rescue_organization.name }}
|
||||||
|
</a>
|
||||||
|
</h2>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
@@ -16,10 +18,10 @@
|
|||||||
{{ rescue_organization.location_string }}
|
{{ rescue_organization.location_string }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="block content">
|
{% if rescue_organization.description_short %}
|
||||||
{% if rescue_organization.description_short %}
|
<div class="block content">
|
||||||
{{ rescue_organization.description_short | render_markdown }}
|
{{ rescue_organization.description_short | render_markdown }}
|
||||||
{% endif %}
|
</div>
|
||||||
</div>
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -23,9 +23,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
{% if dq %}
|
{% if dq %}
|
||||||
<a class="button is-info" href="{% url 'organization-check' %}">{% translate 'Datenergänzung deaktivieren' %}</a>
|
<a class="button is-info"
|
||||||
|
href="{% url 'organization-check' %}">{% translate 'Datenergänzung deaktivieren' %}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a class="button is-info is-light" href="{% url 'organization-check-dq' %}">{% translate 'Datenergänzung aktivieren' %}</a>
|
<a class="button is-info is-light"
|
||||||
|
href="{% url 'organization-check-dq' %}">{% translate 'Datenergänzung aktivieren' %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -36,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>
|
||||||
@@ -48,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>
|
||||||
@@ -60,9 +62,10 @@
|
|||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
|
{% include "fellchensammlung/partials/modal-shortcuts.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -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 %}
|
||||||
@@ -49,7 +49,7 @@ def render_markdown(value):
|
|||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def get_oxitraffic_script_if_enabled():
|
def get_oxitraffic_script_if_enabled():
|
||||||
if settings.OXITRAFFIC_ENABLED:
|
if settings.OXITRAFFIC_ENABLED:
|
||||||
return mark_safe(f'<script type="module" src="https://{settings.OXITRAFFIC_BASE_URL}/count.js"></script>')
|
return mark_safe(f'<script type="module" src="{settings.OXITRAFFIC_BASE_URL}/count.js"></script>')
|
||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from random import randint
|
from random import randint
|
||||||
|
|
||||||
|
from fellchensammlung.tools.model_helpers import AdoptionNoticeStatusChoices
|
||||||
from notfellchen import settings
|
from notfellchen import settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
@@ -10,7 +12,7 @@ from django.template.loader import render_to_string
|
|||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.utils.html import strip_tags
|
from django.utils.html import strip_tags
|
||||||
|
|
||||||
from fellchensammlung.models import AdoptionNotice, Location, RescueOrganization, AdoptionNoticeStatus, Log, \
|
from fellchensammlung.models import AdoptionNotice, Location, RescueOrganization, Log, \
|
||||||
Notification, NotificationTypeChoices
|
Notification, NotificationTypeChoices
|
||||||
from fellchensammlung.tools.misc import is_404
|
from fellchensammlung.tools.misc import is_404
|
||||||
|
|
||||||
@@ -86,7 +88,8 @@ def deactivate_404_adoption_notices():
|
|||||||
for adoption_notice in get_active_adoption_notices():
|
for adoption_notice in get_active_adoption_notices():
|
||||||
if adoption_notice.further_information and adoption_notice.further_information != "":
|
if adoption_notice.further_information and adoption_notice.further_information != "":
|
||||||
if is_404(adoption_notice.further_information):
|
if is_404(adoption_notice.further_information):
|
||||||
adoption_notice.set_closed()
|
adoption_notice.adoption_notice_status = AdoptionNoticeStatusChoices.Closed.LINK_TO_MORE_INFO_NOT_REACHABLE
|
||||||
|
adoption_notice.save()
|
||||||
logging_msg = f"Automatically set Adoption Notice {adoption_notice.id} closed as link to more information returened 404"
|
logging_msg = f"Automatically set Adoption Notice {adoption_notice.id} closed as link to more information returened 404"
|
||||||
logging.info(logging_msg)
|
logging.info(logging_msg)
|
||||||
Log.objects.create(action="automated", text=logging_msg)
|
Log.objects.create(action="automated", text=logging_msg)
|
||||||
|
|||||||
9
src/fellchensammlung/tools/img.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from django.template.loader import render_to_string
|
||||||
|
|
||||||
|
from fellchensammlung.models import AdoptionNotice
|
||||||
|
|
||||||
|
|
||||||
|
def export_svg(adoption_notice):
|
||||||
|
result = render_to_string(template_name="fellchensammlung/images/adoption-notice.svg",
|
||||||
|
context={"adoption_notice": adoption_notice, })
|
||||||
|
return result
|
||||||
@@ -1,4 +1,22 @@
|
|||||||
from fellchensammlung.models import User, AdoptionNotice, AdoptionNoticeStatus
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from fellchensammlung.models import User, AdoptionNotice, AdoptionNoticeStatusChoices, Animal, RescueOrganization
|
||||||
|
|
||||||
|
|
||||||
|
def get_rescue_org_check_stats():
|
||||||
|
timeframe = timezone.now().date() - timedelta(days=14)
|
||||||
|
num_rescue_orgs_to_check = RescueOrganization.objects.filter(exclude_from_check=False).filter(
|
||||||
|
last_checked__lt=timeframe).count()
|
||||||
|
num_rescue_orgs_checked = RescueOrganization.objects.filter(exclude_from_check=False).filter(
|
||||||
|
last_checked__gte=timeframe).count()
|
||||||
|
|
||||||
|
try:
|
||||||
|
percentage_checked = 100 * num_rescue_orgs_checked / (num_rescue_orgs_to_check + num_rescue_orgs_checked)
|
||||||
|
except ZeroDivisionError:
|
||||||
|
percentage_checked = 100
|
||||||
|
return num_rescue_orgs_to_check, num_rescue_orgs_checked, percentage_checked
|
||||||
|
|
||||||
|
|
||||||
def gather_metrics_data():
|
def gather_metrics_data():
|
||||||
@@ -9,14 +27,14 @@ def gather_metrics_data():
|
|||||||
"""Adoption notices"""
|
"""Adoption notices"""
|
||||||
num_adoption_notices = AdoptionNotice.objects.count()
|
num_adoption_notices = AdoptionNotice.objects.count()
|
||||||
adoption_notices_active = AdoptionNotice.objects.filter(
|
adoption_notices_active = AdoptionNotice.objects.filter(
|
||||||
adoptionnoticestatus__major_status=AdoptionNoticeStatus.ACTIVE)
|
adoption_notice_status__in=AdoptionNoticeStatusChoices.Active.values)
|
||||||
num_adoption_notices_active = adoption_notices_active.count()
|
num_adoption_notices_active = adoption_notices_active.count()
|
||||||
num_adoption_notices_closed = AdoptionNotice.objects.filter(
|
num_adoption_notices_closed = AdoptionNotice.objects.filter(
|
||||||
adoptionnoticestatus__major_status=AdoptionNoticeStatus.CLOSED).count()
|
adoption_notice_status__in=AdoptionNoticeStatusChoices.Closed.values).count()
|
||||||
num_adoption_notices_disabled = AdoptionNotice.objects.filter(
|
num_adoption_notices_disabled = AdoptionNotice.objects.filter(
|
||||||
adoptionnoticestatus__major_status=AdoptionNoticeStatus.DISABLED).count()
|
adoption_notice_status__in=AdoptionNoticeStatusChoices.Disabled.values).count()
|
||||||
num_adoption_notices_awaiting_action = AdoptionNotice.objects.filter(
|
num_adoption_notices_awaiting_action = AdoptionNotice.objects.filter(
|
||||||
adoptionnoticestatus__major_status=AdoptionNoticeStatus.AWAITING_ACTION).count()
|
adoption_notice_status__in=AdoptionNoticeStatusChoices.AwaitingAction.values).count()
|
||||||
|
|
||||||
adoption_notices_without_location = AdoptionNotice.objects.filter(location__isnull=True).count()
|
adoption_notices_without_location = AdoptionNotice.objects.filter(location__isnull=True).count()
|
||||||
|
|
||||||
@@ -32,6 +50,10 @@ def gather_metrics_data():
|
|||||||
active_animals_per_sex[sex] = number_of_animals
|
active_animals_per_sex[sex] = number_of_animals
|
||||||
active_animals += number_of_animals
|
active_animals += number_of_animals
|
||||||
|
|
||||||
|
num_animal_shelters = RescueOrganization.objects.all().count()
|
||||||
|
|
||||||
|
num_rescue_orgs_to_check, num_rescue_orgs_checked, percentage_checked = get_rescue_org_check_stats()
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'users': num_user,
|
'users': num_user,
|
||||||
'staff': num_staff,
|
'staff': num_staff,
|
||||||
@@ -45,6 +67,12 @@ def gather_metrics_data():
|
|||||||
},
|
},
|
||||||
'adoption_notices_without_location': adoption_notices_without_location,
|
'adoption_notices_without_location': adoption_notices_without_location,
|
||||||
'active_animals': active_animals,
|
'active_animals': active_animals,
|
||||||
'active_animals_per_sex': active_animals_per_sex
|
'active_animals_per_sex': active_animals_per_sex,
|
||||||
|
'rescue_organizations': num_animal_shelters,
|
||||||
|
'rescue_organization_check': {
|
||||||
|
'rescue_orgs_to_check': num_rescue_orgs_to_check,
|
||||||
|
'rescue_orgs_checked': num_rescue_orgs_checked,
|
||||||
|
'percentage_checked': percentage_checked,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return data
|
return data
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ def pluralize(number, letter="e"):
|
|||||||
|
|
||||||
|
|
||||||
def age_as_hr_string(age: datetime.timedelta) -> str:
|
def age_as_hr_string(age: datetime.timedelta) -> str:
|
||||||
days = age.days
|
days = int(age.days)
|
||||||
weeks = age.days / 7
|
weeks = int(age.days / 7)
|
||||||
months = age.days / 30
|
months = int(age.days / 30)
|
||||||
years = age.days / 365
|
years = int(age.days / 365)
|
||||||
if years >= 1:
|
if years >= 1:
|
||||||
months = months - 12 * years
|
months = months - 12 * years
|
||||||
return f'{years:.0f} Jahr{pluralize(years)} und {months:.0f} Monat{pluralize(months)}'
|
return f'{years:.0f} Jahr{pluralize(years)} und {months:.0f} Monat{pluralize(months)}'
|
||||||
@@ -52,9 +52,9 @@ def time_since_as_hr_string(age: datetime.timedelta) -> str:
|
|||||||
elif weeks >= 3:
|
elif weeks >= 3:
|
||||||
text = _("vor %(weeks)d Wochen") % {"weeks": weeks}
|
text = _("vor %(weeks)d Wochen") % {"weeks": weeks}
|
||||||
elif days >= 1:
|
elif days >= 1:
|
||||||
text = ngettext("vor einem Tag","vor %(count)d Tagen", days,) % {"count": days,}
|
text = ngettext("vor einem Tag", "vor %(count)d Tagen", days, ) % {"count": days, }
|
||||||
elif hours >= 1:
|
elif hours >= 1:
|
||||||
text = ngettext("vor einer Stunde", "vor %(count)d Stunden", hours,) % {"count": hours,}
|
text = ngettext("vor einer Stunde", "vor %(count)d Stunden", hours, ) % {"count": hours, }
|
||||||
elif minutes >= 1:
|
elif minutes >= 1:
|
||||||
text = ngettext("vor einer Minute", "vor %(count)d Minuten", minutes, ) % {"count": minutes, }
|
text = ngettext("vor einer Minute", "vor %(count)d Minuten", minutes, ) % {"count": minutes, }
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from django.db.models.enums import TextChoices
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
@@ -30,8 +31,8 @@ report_mapping = NotificationDisplayMapping(
|
|||||||
)
|
)
|
||||||
# ndm = notification display mapping
|
# ndm = notification display mapping
|
||||||
ndm = {NotificationTypeChoices.NEW_USER: NotificationDisplayMapping(
|
ndm = {NotificationTypeChoices.NEW_USER: NotificationDisplayMapping(
|
||||||
email_html_template='fellchensammlung/mail/notifications/report.html',
|
email_html_template='fellchensammlung/mail/notifications/new-user.html',
|
||||||
email_plain_template="fellchensammlung/mail/notifications/report.txt",
|
email_plain_template="fellchensammlung/mail/notifications/new-user.txt",
|
||||||
web_partial="fellchensammlung/partials/notifications/body-new-user.html"),
|
web_partial="fellchensammlung/partials/notifications/body-new-user.html"),
|
||||||
NotificationTypeChoices.NEW_COMMENT: NotificationDisplayMapping(
|
NotificationTypeChoices.NEW_COMMENT: NotificationDisplayMapping(
|
||||||
email_html_template='fellchensammlung/mail/notifications/new-comment.html',
|
email_html_template='fellchensammlung/mail/notifications/new-comment.html',
|
||||||
@@ -55,3 +56,81 @@ ndm = {NotificationTypeChoices.NEW_USER: NotificationDisplayMapping(
|
|||||||
web_partial='fellchensammlung/partials/notifications/body-an-for-search.html'
|
web_partial='fellchensammlung/partials/notifications/body-an-for-search.html'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AdoptionNoticeStatusChoices:
|
||||||
|
class Active(TextChoices):
|
||||||
|
SEARCHING = "active_searching", _("Searching")
|
||||||
|
INTERESTED = "active_interested", _("Interested")
|
||||||
|
|
||||||
|
class AwaitingAction(TextChoices):
|
||||||
|
WAITING_FOR_REVIEW = "awaiting_action_waiting_for_review", _("Waiting for review")
|
||||||
|
NEEDS_ADDITIONAL_INFO = "awaiting_action_needs_additional_info", _("Needs additional info")
|
||||||
|
UNCHECKED = "awaiting_action_unchecked", _("Unchecked")
|
||||||
|
|
||||||
|
class Closed(TextChoices):
|
||||||
|
SUCCESSFUL_WITH_NOTFELLCHEN = "closed_successful_with_notfellchen", _("Successful (with Notfellchen)")
|
||||||
|
SUCCESSFUL_WITHOUT_NOTFELLCHEN = "closed_successful_without_notfellchen", _("Successful (without Notfellchen)")
|
||||||
|
ANIMAL_DIED = "closed_animal_died", _("Animal died")
|
||||||
|
FOR_OTHER_ADOPTION_NOTICE = "closed_for_other_adoption_notice", _("Closed for other adoption notice")
|
||||||
|
NOT_OPEN_ANYMORE = "closed_not_open_for_adoption_anymore", _("Not open for adoption anymore")
|
||||||
|
LINK_TO_MORE_INFO_NOT_REACHABLE = "closed_link_to_more_info_not_reachable", _(
|
||||||
|
"Der Link zu weiteren Informationen ist nicht mehr erreichbar.")
|
||||||
|
OTHER = "closed_other", _("Other (closed)")
|
||||||
|
|
||||||
|
class Disabled(TextChoices):
|
||||||
|
AGAINST_RULES = "disabled_against_the_rules", _("Against the rules")
|
||||||
|
OTHER = "disabled_other", _("Other (disabled)")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def all_choices(cls):
|
||||||
|
"""Return all subgroup choices as a single list for use in models."""
|
||||||
|
return (
|
||||||
|
cls.Active.choices
|
||||||
|
+ cls.AwaitingAction.choices
|
||||||
|
+ cls.Closed.choices
|
||||||
|
+ cls.Disabled.choices
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AdoptionNoticeStatusChoicesDescriptions:
|
||||||
|
_ansc = AdoptionNoticeStatusChoices # Mapping for readability
|
||||||
|
mapping = {_ansc.Active.SEARCHING.value: "",
|
||||||
|
_ansc.Active.INTERESTED: _("Jemand hat bereits Interesse an den Tieren."),
|
||||||
|
_ansc.Closed.SUCCESSFUL_WITH_NOTFELLCHEN: _("Vermittlung erfolgreich abgeschlossen."),
|
||||||
|
_ansc.Closed.SUCCESSFUL_WITHOUT_NOTFELLCHEN: _("Vermittlung erfolgreich abgeschlossen."),
|
||||||
|
_ansc.Closed.ANIMAL_DIED: _("Die zu vermittelnden Tiere sind über die Regenbrücke gegangen."),
|
||||||
|
_ansc.Closed.FOR_OTHER_ADOPTION_NOTICE: _("Vermittlung wurde zugunsten einer anderen geschlossen."),
|
||||||
|
_ansc.Closed.NOT_OPEN_ANYMORE: _("Tier(e) stehen nicht mehr zur Vermittlung bereit."),
|
||||||
|
_ansc.Closed.LINK_TO_MORE_INFO_NOT_REACHABLE: _(
|
||||||
|
"Der Link zu weiteren Informationen ist nicht mehr erreichbar,"
|
||||||
|
"die Vermittlung wurde daher automatisch deaktiviert"),
|
||||||
|
_ansc.Closed.OTHER: _("Vermittlung geschlossen."),
|
||||||
|
|
||||||
|
_ansc.AwaitingAction.WAITING_FOR_REVIEW: _(
|
||||||
|
"Deaktiviert bis Moderator*innen die Vermittlung prüfen können."),
|
||||||
|
_ansc.AwaitingAction.NEEDS_ADDITIONAL_INFO: _("Deaktiviert bis Informationen nachgetragen werden."),
|
||||||
|
_ansc.AwaitingAction.UNCHECKED: _(
|
||||||
|
"Vermittlung deaktiviert bis sie vom Team auf Aktualität geprüft wurde."),
|
||||||
|
|
||||||
|
_ansc.Disabled.AGAINST_RULES: _("Vermittlung deaktiviert da sie gegen die Regeln verstößt."),
|
||||||
|
_ansc.Disabled.OTHER: _("Vermittlung deaktiviert.")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AdoptionProcess(TextChoices):
|
||||||
|
CONTACT_PERSON_IN_AN = "contact_person_in_an", _("Kontaktiere die Person im Vermittlungstext")
|
||||||
|
|
||||||
|
|
||||||
|
class AdoptionNoticeProcessTemplates:
|
||||||
|
_bp = "fellchensammlung/partials/adoption_process/" # Base path for ease
|
||||||
|
mapping = {AdoptionProcess.CONTACT_PERSON_IN_AN: f"{_bp}contact_person_in_an.html",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class RegularCheckStatusChoices(models.TextChoices):
|
||||||
|
REGULAR_CHECK = "regular_check", _("Wird regelmäßig geprüft")
|
||||||
|
EXCLUDED_NO_ONLINE_LISTING = "excluded_no_online_listing", _("Exkludiert: Tiere werden nicht online gelistet")
|
||||||
|
EXCLUDED_OTHER_ORG = "excluded_other_org", _("Exkludiert: Andere Organisation wird geprüft")
|
||||||
|
EXCLUDED_SCOPE = "excluded_scope", _("Exkludiert: Organisation hat nie Notfellchen-relevanten Vermittlungen")
|
||||||
|
EXCLUDED_OTHER = "excluded_other", _("Exkludiert: Anderer Grund")
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
from fellchensammlung.models import User, Notification, TrustLevel
|
from fellchensammlung.models import User, Notification, TrustLevel, AdoptionNoticeStatusChoices
|
||||||
from fellchensammlung.models import NotificationTypeChoices as ntc
|
from fellchensammlung.models import NotificationTypeChoices as ntc
|
||||||
|
|
||||||
|
|
||||||
def notify_of_AN_to_be_checked(adoption_notice):
|
def notify_of_AN_to_be_checked(adoption_notice):
|
||||||
if adoption_notice.is_disabled_unchecked:
|
if adoption_notice.adoption_notice_status == AdoptionNoticeStatusChoices.AwaitingAction.WAITING_FOR_REVIEW:
|
||||||
users_to_notify = set(User.objects.filter(trust_level__gt=TrustLevel.MODERATOR))
|
users_to_notify = set(User.objects.filter(trust_level__gt=TrustLevel.MODERATOR))
|
||||||
users_to_notify.add(adoption_notice.owner)
|
users_to_notify.add(adoption_notice.owner)
|
||||||
for user in users_to_notify:
|
for user in users_to_notify:
|
||||||
|
|||||||