Compare commits
165 Commits
20cbb0397a
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| 151ce0d88e | |||
| e07e633651 | |||
| dd3b1fde9d | |||
| 2ffc9b4ba1 | |||
| 22eebd4586 | |||
| e589a048d3 | |||
| 392eb5a7a8 | |||
| 44fa4d4880 | |||
| 9b97cc4cb1 | |||
| 656a24ef02 | |||
| 74643db087 | |||
| 3a6fd3cee1 | |||
| 29e9d1bd8c | |||
| 3c5ca9ae00 | |||
| 3d1ad6112d | |||
| b843e67e9b | |||
| 4cab71e8fb | |||
| 969339a95f | |||
| e06efa1539 | |||
| 2fb6d2782f | |||
| f69eccd0e4 | |||
| e20e6d4b1d | |||
| 0352a60e28 | |||
| abeb14601a | |||
| f52225495d | |||
| 797b2c15f7 | |||
| e81618500b | |||
| f7a5da306c | |||
| 92a9b5c6c9 | |||
| 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 | |||
| a372be4af2 | |||
| 5d333b28ab | |||
| 84ad047c01 | |||
| c93b2631cb | |||
| 15dd06a91f | |||
| 30ff26c7ef | |||
| 1434e7502a | |||
| 93b21fb7d0 | |||
| e5c82f392c | |||
| 0626964461 | |||
| 23a724e390 | |||
| 2a9c7cf854 | |||
| 335630e16d | |||
| 6051f7c294 | |||
| c1ea6cd211 | |||
| 6c43b46007 | |||
| dc9e68c4b9 | |||
| 4b03f99971 | |||
| 426f4b3d8b | |||
| 3604233507 | |||
| 8c5099f14a | |||
| d5bc348453 | |||
| bce98cb439 | |||
| 1ed3d27533 | |||
| 39a098af8e | |||
| 62491b84c1 | |||
| 81f7f5bb5d | |||
| 8ce4122160 | |||
| 370ad2ce66 | |||
| f25c425d85 | |||
| d921623f31 | |||
| 2589f1c703 | |||
| 0edb9094c4 | |||
| bc8feba701 | |||
| f37d74a7d1 | |||
| fa8612ad1a | |||
| 1d8a054b06 | |||
| 5898fbf86d | |||
| cd1cdd2e0b | |||
| c0f920544b | |||
| 36c90531a8 | |||
| 7f7c5a3b04 | |||
| c084e56ad8 | |||
| 84acc3c76e | |||
| e1f0014898 | |||
| 05b3a470f3 | |||
| ebe060646a | |||
| bb412be8d3 | |||
| e3c48eac24 | |||
| da89cdceda | |||
| 5a6c2c99e5 | |||
| 9f53836ce8 | |||
| 5d53d1a1dc | |||
| e00dda1dc2 | |||
| a93e0c819f | |||
| c87733b37a | |||
| 9aa964bf05 | |||
| dcb1d3ec15 | |||
| 5d9b8f3213 | |||
| d12989d195 | |||
| a9f384b50e | |||
| afedf2d0bd | |||
| a4b8486bd4 | |||
| d8bcb8ece6 | |||
| b01ac219a3 | |||
| 42320866c4 | |||
| e2e6c14d57 | |||
| 4761c38cd2 | |||
| e2bef3efe2 | |||
| bbfd4c3800 | |||
| b671d8fbb4 | |||
| 1ea04e98e8 | |||
| c1a7d6790b | |||
| f519f78922 | |||
| 551b5ed6be |
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.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ host=localhost
|
|||||||
[django]
|
[django]
|
||||||
secret=CHANGE-ME
|
secret=CHANGE-ME
|
||||||
debug=True
|
debug=True
|
||||||
|
internal_ips=["127.0.0.1"]
|
||||||
|
|
||||||
[database]
|
[database]
|
||||||
backend=sqlite3
|
backend=sqlite3
|
||||||
@@ -28,3 +29,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"]
|
||||||
@@ -48,6 +49,7 @@ develop = [
|
|||||||
"pytest",
|
"pytest",
|
||||||
"coverage",
|
"coverage",
|
||||||
"model_bakery",
|
"model_bakery",
|
||||||
|
"debug_toolbar",
|
||||||
]
|
]
|
||||||
docs = [
|
docs = [
|
||||||
"sphinx",
|
"sphinx",
|
||||||
|
|||||||
@@ -178,8 +178,13 @@ def create_location(tierheim, instance, headers):
|
|||||||
location_result = requests.post(f"{instance}/api/locations/", json=location_data, headers=headers)
|
location_result = requests.post(f"{instance}/api/locations/", json=location_data, headers=headers)
|
||||||
|
|
||||||
if location_result.status_code != 201:
|
if location_result.status_code != 201:
|
||||||
|
try:
|
||||||
print(
|
print(
|
||||||
f"Location for {tierheim["properties"]["name"]}:{location_result.status_code} {location_result.json()} not created")
|
f"Location for {tierheim["properties"]["name"]}:{location_result.status_code} {location_result.json()} not created")
|
||||||
|
except requests.exceptions.JSONDecodeError:
|
||||||
|
print(f"Location for {tierheim["properties"]["name"]} could not be created")
|
||||||
|
exit()
|
||||||
|
|
||||||
return location_result.json()
|
return location_result.json()
|
||||||
|
|
||||||
|
|
||||||
@@ -200,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
|
||||||
@@ -224,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,
|
||||||
@@ -243,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"]}",
|
||||||
@@ -257,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, SpeciesSpecialization
|
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")
|
||||||
|
|
||||||
@@ -100,11 +96,6 @@ class SpeciesSpecificURLInline(admin.StackedInline):
|
|||||||
model = SpeciesSpecificURL
|
model = SpeciesSpecificURL
|
||||||
|
|
||||||
|
|
||||||
class SpeciesSpecializationInline(admin.StackedInline):
|
|
||||||
model = SpeciesSpecialization
|
|
||||||
extra = 0
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(RescueOrganization)
|
@admin.register(RescueOrganization)
|
||||||
class RescueOrganizationAdmin(admin.ModelAdmin):
|
class RescueOrganizationAdmin(admin.ModelAdmin):
|
||||||
search_fields = ("name", "description", "internal_comment", "location_string", "location__city")
|
search_fields = ("name", "description", "internal_comment", "location_string", "location__city")
|
||||||
@@ -112,7 +103,6 @@ class RescueOrganizationAdmin(admin.ModelAdmin):
|
|||||||
list_filter = ("allows_using_materials", "trusted", ("external_source_identifier", EmptyFieldListFilter))
|
list_filter = ("allows_using_materials", "trusted", ("external_source_identifier", EmptyFieldListFilter))
|
||||||
|
|
||||||
inlines = [
|
inlines = [
|
||||||
SpeciesSpecializationInline,
|
|
||||||
SpeciesSpecificURLInline,
|
SpeciesSpecificURLInline,
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -169,6 +159,18 @@ class LocationAdmin(admin.ModelAdmin):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(SocialMediaPost)
|
||||||
|
class SocialMediaPostAdmin(admin.ModelAdmin):
|
||||||
|
list_filter = ("platform",)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Log)
|
||||||
|
class LogAdmin(admin.ModelAdmin):
|
||||||
|
ordering = ["-created_at"]
|
||||||
|
list_filter = ("action",)
|
||||||
|
list_display = ("action", "user", "created_at")
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Animal)
|
admin.site.register(Animal)
|
||||||
admin.site.register(Species)
|
admin.site.register(Species)
|
||||||
admin.site.register(Rule)
|
admin.site.register(Rule)
|
||||||
@@ -176,7 +178,5 @@ 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(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(
|
||||||
@@ -360,9 +361,9 @@ class LocationApiView(APIView):
|
|||||||
|
|
||||||
# Log the action
|
# Log the action
|
||||||
Log.objects.create(
|
Log.objects.create(
|
||||||
user=request.user_to_notify,
|
user=request.user,
|
||||||
action="add_location",
|
action="add_location",
|
||||||
text=f"{request.user_to_notify} added adoption notice {location.pk} via API",
|
text=f"{request.user} added adoption notice {location.pk} via API",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Return success response with new adoption notice details
|
# Return success response with new adoption notice details
|
||||||
@@ -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"),
|
||||||
|
]
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
|
from django.forms.widgets import Textarea
|
||||||
|
|
||||||
from .models import AdoptionNotice, Animal, Image, ReportAdoptionNotice, ReportComment, ModerationAction, User, Species, \
|
from .models import AdoptionNotice, Animal, Image, ReportAdoptionNotice, ReportComment, ModerationAction, User, Species, \
|
||||||
Comment, SexChoicesWithAll, DistanceChoices, SpeciesSpecificURL, RescueOrganization
|
Comment, SexChoicesWithAll, DistanceChoices, SpeciesSpecificURL, RescueOrganization
|
||||||
@@ -9,6 +10,8 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from notfellchen.settings import MEDIA_URL
|
from notfellchen.settings import MEDIA_URL
|
||||||
from crispy_forms.layout import Div
|
from crispy_forms.layout import Div
|
||||||
|
|
||||||
|
from .tools.model_helpers import reason_for_signup_label, reason_for_signup_help_text
|
||||||
|
|
||||||
|
|
||||||
def animal_validator(value: str):
|
def animal_validator(value: str):
|
||||||
value = value.lower()
|
value = value.lower()
|
||||||
@@ -57,6 +60,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 +140,18 @@ class ModerationActionForm(forms.ModelForm):
|
|||||||
fields = ('action', 'public_comment', 'private_comment')
|
fields = ('action', 'public_comment', 'private_comment')
|
||||||
|
|
||||||
|
|
||||||
|
class AddedRegistrationForm(forms.Form):
|
||||||
|
reason_for_signup = forms.CharField(label=reason_for_signup_label,
|
||||||
|
help_text=reason_for_signup_help_text,
|
||||||
|
widget=Textarea)
|
||||||
|
captcha = forms.CharField(validators=[animal_validator], label=_("Nenne eine bekannte Tierart"), help_text=_(
|
||||||
|
"Bitte nenne hier eine bekannte Tierart (z.B. ein Tier das an der Leine geführt wird). Das Fragen wir dich um "
|
||||||
|
"sicherzustellen, dass du kein Roboter bist."))
|
||||||
|
|
||||||
|
def signup(self, request, user):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class CustomRegistrationForm(RegistrationForm):
|
class CustomRegistrationForm(RegistrationForm):
|
||||||
class Meta(RegistrationForm.Meta):
|
class Meta(RegistrationForm.Meta):
|
||||||
model = User
|
model = User
|
||||||
@@ -147,3 +170,11 @@ class AdoptionNoticeSearchForm(forms.Form):
|
|||||||
max_distance = forms.ChoiceField(choices=DistanceChoices, initial=DistanceChoices.ONE_HUNDRED,
|
max_distance = forms.ChoiceField(choices=DistanceChoices, initial=DistanceChoices.ONE_HUNDRED,
|
||||||
label=_("Suchradius"))
|
label=_("Suchradius"))
|
||||||
location_string = forms.CharField(max_length=100, label=_("Stadt"), required=False)
|
location_string = forms.CharField(max_length=100, label=_("Stadt"), required=False)
|
||||||
|
|
||||||
|
|
||||||
|
class RescueOrgSearchForm(forms.Form):
|
||||||
|
template_name = "fellchensammlung/forms/form_snippets.html"
|
||||||
|
|
||||||
|
location_string = forms.CharField(max_length=100, label=_("Stadt"), required=False)
|
||||||
|
max_distance = forms.ChoiceField(choices=DistanceChoices, initial=DistanceChoices.TWENTY,
|
||||||
|
label=_("Suchradius"))
|
||||||
|
|||||||
@@ -7,29 +7,27 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
from fellchensammlung.models import User, Notification, TrustLevel, NotificationTypeChoices
|
from fellchensammlung.models import User, Notification, TrustLevel, NotificationTypeChoices
|
||||||
from notfellchen.settings import base_url
|
from fellchensammlung.tools.model_helpers import ndm
|
||||||
|
|
||||||
NEWLINE = "\r\n"
|
|
||||||
|
|
||||||
|
|
||||||
def mail_admins_new_report(report):
|
def notify_mods_new_report(report, notification_type):
|
||||||
"""
|
"""
|
||||||
Sends an e-mail to all users that should handle the report.
|
Sends an e-mail to all users that should handle the report.
|
||||||
"""
|
"""
|
||||||
for moderator in User.objects.filter(trust_level__gt=TrustLevel.MODERATOR):
|
for moderator in User.objects.filter(trust_level__gt=TrustLevel.MODERATOR):
|
||||||
report_url = base_url + report.get_absolute_url()
|
if notification_type == NotificationTypeChoices.NEW_REPORT_AN:
|
||||||
context = {"report_url": report_url,
|
title = _("Vermittlung gemeldet")
|
||||||
"user_comment": report.user_comment, }
|
elif notification_type == NotificationTypeChoices.NEW_REPORT_COMMENT:
|
||||||
|
title = _("Kommentar gemeldet")
|
||||||
subject = _("Neue Meldung")
|
else:
|
||||||
html_message = render_to_string('fellchensammlung/mail/notifications/report.html', context)
|
raise NotImplementedError
|
||||||
plain_message = strip_tags(html_message)
|
notification = Notification.objects.create(
|
||||||
|
notification_type=notification_type,
|
||||||
mail.send_mail(subject,
|
user_to_notify=moderator,
|
||||||
plain_message,
|
report=report,
|
||||||
from_email="info@notfellchen.org",
|
title=title,
|
||||||
recipient_list=[moderator.email],
|
)
|
||||||
html_message=html_message)
|
notification.save()
|
||||||
|
|
||||||
|
|
||||||
def send_notification_email(notification_pk):
|
def send_notification_email(notification_pk):
|
||||||
@@ -37,24 +35,9 @@ def send_notification_email(notification_pk):
|
|||||||
|
|
||||||
subject = f"{notification.title}"
|
subject = f"{notification.title}"
|
||||||
context = {"notification": notification, }
|
context = {"notification": notification, }
|
||||||
if notification.notification_type == NotificationTypeChoices.NEW_REPORT_COMMENT or notification.notification_type == NotificationTypeChoices.NEW_REPORT_AN:
|
html_message = render_to_string(ndm[notification.notification_type].email_html_template, context)
|
||||||
context["user_comment"] = notification.report.user_comment
|
plain_message = render_to_string(ndm[notification.notification_type].email_plain_template, context)
|
||||||
context["report_url"] = f"{base_url}{notification.report.get_absolute_url()}"
|
|
||||||
html_message = render_to_string('fellchensammlung/mail/notifications/report.html', context)
|
|
||||||
elif notification.notification_type == NotificationTypeChoices.NEW_USER:
|
|
||||||
html_message = render_to_string('fellchensammlung/mail/notifications/new-user.html', context)
|
|
||||||
elif notification.notification_type == NotificationTypeChoices.AN_IS_TO_BE_CHECKED:
|
|
||||||
html_message = render_to_string('fellchensammlung/mail/notifications/an-to-be-checked.html', context)
|
|
||||||
elif notification.notification_type == NotificationTypeChoices.AN_WAS_DEACTIVATED:
|
|
||||||
html_message = render_to_string('fellchensammlung/mail/notifications/an-deactivated.html', context)
|
|
||||||
elif notification.notification_type == NotificationTypeChoices.AN_FOR_SEARCH_FOUND:
|
|
||||||
html_message = render_to_string('fellchensammlung/mail/notifications/an-for-search-found.html', context)
|
|
||||||
elif notification.notification_type == NotificationTypeChoices.NEW_COMMENT:
|
|
||||||
html_message = render_to_string('fellchensammlung/mail/notifications/new-comment.html', context)
|
|
||||||
else:
|
|
||||||
raise NotImplementedError("Unknown notification type")
|
|
||||||
|
|
||||||
plain_message = strip_tags(html_message)
|
|
||||||
mail.send_mail(subject, plain_message, settings.DEFAULT_FROM_EMAIL,
|
mail.send_mail(subject, plain_message, settings.DEFAULT_FROM_EMAIL,
|
||||||
[notification.user_to_notify.email],
|
[notification.user_to_notify.email],
|
||||||
html_message=html_message)
|
html_message=html_message)
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
from django.core.management import BaseCommand
|
||||||
|
from fellchensammlung.tools.admin import mask_organization_contact_data
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Mask e-mail addresses and phone numbers of organizations for testing purposes.'
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument("domain", type=str)
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
domain = options["domain"]
|
||||||
|
mask_organization_contact_data(domain)
|
||||||
19
src/fellchensammlung/management/commands/sync_to_twenty.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from django.core.management import BaseCommand
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
from fellchensammlung.models import RescueOrganization
|
||||||
|
from fellchensammlung.tools.twenty import sync_rescue_org_to_twenty
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Send rescue organizations as companies to twenty'
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument("base_url", type=str)
|
||||||
|
parser.add_argument("token", type=str)
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
base_url = options["base_url"]
|
||||||
|
token = options["token"]
|
||||||
|
for rescue_org in tqdm(RescueOrganization.objects.all()):
|
||||||
|
sync_rescue_org_to_twenty(rescue_org, base_url, token)
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-07-13 10:54
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fellchensammlung', '0054_alter_notification_comment'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rescueorganization',
|
||||||
|
name='ongoing_communication',
|
||||||
|
field=models.BooleanField(default=False, help_text='Es findet gerade Kommunikation zwischen Notfellchen und der Organisation statt.', verbose_name='In aktiver Kommunikation'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='notification',
|
||||||
|
name='user_to_notify',
|
||||||
|
field=models.ForeignKey(help_text='Useraccount der Benachrichtigt wird', on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL, verbose_name='Empfänger*in'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-07-14 05:12
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fellchensammlung', '0055_rescueorganization_ongoing_communication_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='rescueorganization',
|
||||||
|
options={'ordering': ['name']},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rescueorganization',
|
||||||
|
name='specializations',
|
||||||
|
field=models.ManyToManyField(to='fellchensammlung.species'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-07-14 05:15
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fellchensammlung', '0056_alter_rescueorganization_options_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='SpeciesSpecialization',
|
||||||
|
),
|
||||||
|
]
|
||||||
25
src/fellchensammlung/migrations/0058_socialmediapost.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-07-19 17:48
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fellchensammlung', '0057_delete_speciesspecialization'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SocialMediaPost',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created_at', models.DateField(default=django.utils.timezone.now, verbose_name='Erstellt am')),
|
||||||
|
('platform', models.CharField(choices=[('fediverse', 'Fediverse')], max_length=255, verbose_name='Social Media Platform')),
|
||||||
|
('url', models.URLField(verbose_name='URL')),
|
||||||
|
('adoption_notice', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.adoptionnotice', verbose_name='Vermittlung')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-08-02 09:33
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fellchensammlung', '0058_socialmediapost'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rescueorganization',
|
||||||
|
name='twenty_id',
|
||||||
|
field=models.UUIDField(blank=True, help_text='ID der der Organisation in Twenty', null=True, verbose_name='Twenty-ID'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='rescueorganization',
|
||||||
|
name='specializations',
|
||||||
|
field=models.ManyToManyField(blank=True, to='fellchensammlung.species'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-08-30 21:49
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fellchensammlung', '0059_rescueorganization_twenty_id_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='adoptionnotice',
|
||||||
|
options={'permissions': [('create_active_adoption_notice', 'Can create an active adoption notice')], 'verbose_name': 'Vermittlung', 'verbose_name_plural': 'Vermittlungen'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='adoptionnoticestatus',
|
||||||
|
options={'verbose_name': 'Vermittlungsstatus', 'verbose_name_plural': 'Vermittlungsstati'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='animal',
|
||||||
|
options={'verbose_name': 'Tier', 'verbose_name_plural': 'Tiere'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='announcement',
|
||||||
|
options={'verbose_name': 'Banner', 'verbose_name_plural': 'Banner'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='comment',
|
||||||
|
options={'verbose_name': 'Kommentar', 'verbose_name_plural': 'Kommentare'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='image',
|
||||||
|
options={'verbose_name': 'Bild', 'verbose_name_plural': 'Bilder'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='importantlocation',
|
||||||
|
options={'verbose_name': 'Wichtiger Standort', 'verbose_name_plural': 'Wichtige Standorte'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='location',
|
||||||
|
options={'verbose_name': 'Standort', 'verbose_name_plural': 'Standorte'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='moderationaction',
|
||||||
|
options={'verbose_name': 'Moderationsaktion', 'verbose_name_plural': 'Moderationsaktionen'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='notification',
|
||||||
|
options={'verbose_name': 'Benachrichtigung', 'verbose_name_plural': 'Benachrichtigungen'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='report',
|
||||||
|
options={'verbose_name': 'Meldung', 'verbose_name_plural': 'Meldungen'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='rescueorganization',
|
||||||
|
options={'ordering': ['name'], 'verbose_name': 'Tierschutzorganisation', 'verbose_name_plural': 'Tierschutzorganisationen'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='rule',
|
||||||
|
options={'verbose_name': 'Regel', 'verbose_name_plural': 'Regeln'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='searchsubscription',
|
||||||
|
options={'verbose_name': 'Abonnierte Suche', 'verbose_name_plural': 'Abonnierte Suchen'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='speciesspecificurl',
|
||||||
|
options={'verbose_name': 'Tierartspezifische URL', 'verbose_name_plural': 'Tierartspezifische URLs'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='subscriptions',
|
||||||
|
options={'verbose_name': 'Abonnement', 'verbose_name_plural': 'Abonnements'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='timestamp',
|
||||||
|
options={'verbose_name': 'Zeitstempel', 'verbose_name_plural': 'Zeitstempel'},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='adoptionnotice',
|
||||||
|
name='adoption_notice_status',
|
||||||
|
field=models.TextField(choices=[('active_searching', 'Searching'), ('active_interested', 'Interested'), ('awaiting_action_waiting_for_review', 'Waiting for review'), ('awaiting_action_needs_additional_info', 'Needs additional info'), ('closed_successful_with_notfellchen', 'Successful (with Notfellchen)'), ('closed_successful_without_notfellchen', 'Successful (without Notfellchen)'), ('closed_animal_died', 'Animal died'), ('closed_for_other_adoption_notice', 'Closed for other adoption notice'), ('closed_not_open_for_adoption_anymore', 'Not open for adoption anymore'), ('closed_other', 'Other (closed)'), ('disabled_against_the_rules', 'Against the rules'), ('disabled_unchecked', 'Unchecked'), ('disabled_other', 'Other (disabled)')], default='disabled_other', max_length=64, verbose_name='Status'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-08-30 21:51
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def map_status(adoption_notice_status):
|
||||||
|
minor = adoption_notice_status.minor_status
|
||||||
|
|
||||||
|
if minor == "searching":
|
||||||
|
return "active_searching"
|
||||||
|
if minor == "interested":
|
||||||
|
return "active_interested"
|
||||||
|
|
||||||
|
if minor == "waiting_for_review":
|
||||||
|
return "awaiting_action_waiting_for_review"
|
||||||
|
if minor == "needs_additional_info":
|
||||||
|
return "awaiting_action_needs_additional_info"
|
||||||
|
|
||||||
|
if minor == "successful_with_notfellchen":
|
||||||
|
return "closed_successful_with_notfellchen"
|
||||||
|
if minor == "successful_without_notfellchen":
|
||||||
|
return "closed_successful_without_notfellchen"
|
||||||
|
if minor == "animal_died":
|
||||||
|
return "closed_animal_died"
|
||||||
|
if minor == "closed_for_other_adoption_notice":
|
||||||
|
return "closed_for_other_adoption_notice"
|
||||||
|
if minor == "not_open_for_adoption_anymore":
|
||||||
|
return "closed_not_open_for_adoption_anymore"
|
||||||
|
if minor == "other":
|
||||||
|
return "closed_other"
|
||||||
|
|
||||||
|
if minor == "against_the_rules":
|
||||||
|
return "disabled_against_the_rules"
|
||||||
|
if minor == "unchecked":
|
||||||
|
return "disabled_unchecked"
|
||||||
|
if minor in ["missing_information", "technical_error"]:
|
||||||
|
return "disabled_other"
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_status(apps, schema_editor):
|
||||||
|
# We can't import the model directly as it may be a newer
|
||||||
|
# version than this migration expects. We use the historical version.
|
||||||
|
AdoptionNoticeStatus = apps.get_model("fellchensammlung", "AdoptionNoticeStatus")
|
||||||
|
AdoptionNotice = apps.get_model("fellchensammlung", "AdoptionNotice")
|
||||||
|
for ans in AdoptionNoticeStatus.objects.all():
|
||||||
|
adoption_notice = AdoptionNotice.objects.get(id=ans.adoption_notice.id)
|
||||||
|
new_status = map_status(ans)
|
||||||
|
logging.debug(f"{ans.minor_status} -> {new_status}")
|
||||||
|
adoption_notice.adoption_notice_status = map_status(ans)
|
||||||
|
adoption_notice.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('fellchensammlung', '0060_alter_adoptionnotice_options_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(migrate_status),
|
||||||
|
]
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-08-30 22:50
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fellchensammlung', '0061_datamigration_status_model_to_field'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='adoptionnotice',
|
||||||
|
name='adoption_notice_status',
|
||||||
|
field=models.TextField(choices=[('active_searching', 'Searching'), ('active_interested', 'Interested'), ('awaiting_action_waiting_for_review', 'Waiting for review'), ('awaiting_action_needs_additional_info', 'Needs additional info'), ('closed_successful_with_notfellchen', 'Successful (with Notfellchen)'), ('closed_successful_without_notfellchen', 'Successful (without Notfellchen)'), ('closed_animal_died', 'Animal died'), ('closed_for_other_adoption_notice', 'Closed for other adoption notice'), ('closed_not_open_for_adoption_anymore', 'Not open for adoption anymore'), ('closed_link_to_more_info_not_reachable', 'Der Link zu weiteren Informationen ist nicht mehr erreichbar.'), ('closed_other', 'Other (closed)'), ('disabled_against_the_rules', 'Against the rules'), ('disabled_unchecked', 'Unchecked'), ('disabled_other', 'Other (disabled)')], max_length=64, verbose_name='Status'),
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='AdoptionNoticeStatus',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-09-05 14:04
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fellchensammlung', '0062_alter_adoptionnotice_adoption_notice_status_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='adoptionnotice',
|
||||||
|
name='adoption_process',
|
||||||
|
field=models.TextField(blank=True, choices=[('contact_person_in_an', 'Kontaktiere die Person im Vermittlungstext')], max_length=64, null=True, verbose_name='Adoptionsprozess'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-09-06 11:11
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fellchensammlung', '0063_adoptionnotice_adoption_process'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='animal',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=200, verbose_name='Name'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='animal',
|
||||||
|
name='photos',
|
||||||
|
field=models.ManyToManyField(blank=True, to='fellchensammlung.image', verbose_name='Fotos'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='animal',
|
||||||
|
name='sex',
|
||||||
|
field=models.CharField(choices=[('F', 'Weiblich'), ('M', 'Männlich'), ('M_N', 'Männlich, kastriert'), ('F_N', 'Weiblich, kastriert'), ('I', 'Intergeschlechtlich')], max_length=20, verbose_name='Geschlecht'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='comment',
|
||||||
|
name='adoption_notice',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.adoptionnotice', verbose_name='Vermittlung'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='image',
|
||||||
|
name='alt_text',
|
||||||
|
field=models.TextField(help_text='Beschreibe das Bild für blinde und sehbehinderte Menschen', max_length=2000, verbose_name='Alternativtext'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='location',
|
||||||
|
name='city',
|
||||||
|
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Stadt'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='location',
|
||||||
|
name='county',
|
||||||
|
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Landkreis'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='location',
|
||||||
|
name='housenumber',
|
||||||
|
field=models.CharField(blank=True, max_length=20, null=True, verbose_name='Hausnummer'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='location',
|
||||||
|
name='latitude',
|
||||||
|
field=models.FloatField(verbose_name='Breitengrad'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='location',
|
||||||
|
name='longitude',
|
||||||
|
field=models.FloatField(verbose_name='Längengrad'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='location',
|
||||||
|
name='postcode',
|
||||||
|
field=models.CharField(blank=True, max_length=20, null=True, verbose_name='Postleitzahl'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='location',
|
||||||
|
name='street',
|
||||||
|
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Straße'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='notification',
|
||||||
|
name='user_to_notify',
|
||||||
|
field=models.ForeignKey(help_text='Useraccount der benachrichtigt wird', on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL, verbose_name='Empfänger*in'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='report',
|
||||||
|
name='created_at',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, verbose_name='Erstellt am'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='report',
|
||||||
|
name='updated_at',
|
||||||
|
field=models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert am'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='rule',
|
||||||
|
name='created_at',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, verbose_name='Erstellt am'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='rule',
|
||||||
|
name='language',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='fellchensammlung.language', verbose_name='Sprache'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='rule',
|
||||||
|
name='rule_identifier',
|
||||||
|
field=models.CharField(help_text='Ein eindeutiger Identifikator der Regel. Ein Regelobjekt derselben Regel in einer anderen Sprache muss den gleichen Identifikator haben', max_length=24, verbose_name='Regel-ID'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='rule',
|
||||||
|
name='rule_text',
|
||||||
|
field=models.TextField(verbose_name='Regeltext'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='rule',
|
||||||
|
name='updated_at',
|
||||||
|
field=models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert am'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='searchsubscription',
|
||||||
|
name='created_at',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, verbose_name='Erstellt am'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='searchsubscription',
|
||||||
|
name='sex',
|
||||||
|
field=models.CharField(choices=[('F', 'Weiblich'), ('M', 'Männlich'), ('M_N', 'Männlich, kastriert'), ('F_N', 'Weiblich Kastriert'), ('I', 'Intergeschlechtlich'), ('A', 'Alle')], max_length=20, verbose_name='Geschlecht'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='searchsubscription',
|
||||||
|
name='updated_at',
|
||||||
|
field=models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert am'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='subscriptions',
|
||||||
|
name='adoption_notice',
|
||||||
|
field=models.ForeignKey(help_text='Vermittlung die abonniert wurde', on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.adoptionnotice', verbose_name='Vermittlung'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='text',
|
||||||
|
name='title',
|
||||||
|
field=models.CharField(max_length=100, verbose_name='Titel'),
|
||||||
|
),
|
||||||
|
]
|
||||||
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,22 +1,21 @@
|
|||||||
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, AdoptionNoticeStatusChoices, AdoptionProcess, \
|
||||||
|
AdoptionNoticeStatusChoicesDescriptions, RegularCheckStatusChoices, reason_for_signup_label, \
|
||||||
|
reason_for_signup_help_text
|
||||||
|
from .tools.model_helpers import ndm as NotificationDisplayMapping
|
||||||
|
|
||||||
|
|
||||||
class Language(models.Model):
|
class Language(models.Model):
|
||||||
@@ -42,14 +41,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"),
|
||||||
@@ -58,6 +57,10 @@ class Location(models.Model):
|
|||||||
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)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Standort")
|
||||||
|
verbose_name_plural = _("Standorte")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.city and self.postcode:
|
if self.city and self.postcode:
|
||||||
return f"{self.city} ({self.postcode})"
|
return f"{self.city} ({self.postcode})"
|
||||||
@@ -101,10 +104,17 @@ class Location(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class ImportantLocation(models.Model):
|
class ImportantLocation(models.Model):
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Wichtiger Standort")
|
||||||
|
verbose_name_plural = _("Wichtige Standorte")
|
||||||
|
|
||||||
location = models.OneToOneField(Location, on_delete=models.CASCADE)
|
location = models.OneToOneField(Location, on_delete=models.CASCADE)
|
||||||
slug = models.SlugField(unique=True)
|
slug = models.SlugField(unique=True)
|
||||||
name = models.CharField(max_length=200)
|
name = models.CharField(max_length=200)
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('search-by-location', kwargs={'important_location_slug': self.slug})
|
||||||
|
|
||||||
|
|
||||||
class ExternalSourceChoices(models.TextChoices):
|
class ExternalSourceChoices(models.TextChoices):
|
||||||
OSM = "OSM", _("Open Street Map")
|
OSM = "OSM", _("Open Street Map")
|
||||||
@@ -118,10 +128,24 @@ class AllowUseOfMaterialsChices(models.TextChoices):
|
|||||||
USE_MATERIALS_NOT_ASKED = "not_asked", _("Not asked")
|
USE_MATERIALS_NOT_ASKED = "not_asked", _("Not asked")
|
||||||
|
|
||||||
|
|
||||||
class RescueOrganization(models.Model):
|
class Species(models.Model):
|
||||||
def __str__(self):
|
"""Model representing a species of animal."""
|
||||||
return f"{self.name}"
|
name = models.CharField(max_length=200, help_text=_('Name der Tierart'),
|
||||||
|
verbose_name=_('Name'))
|
||||||
|
slug = models.SlugField(unique=True, verbose_name=_('Slug'))
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""String for representing the Model object."""
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Tierart')
|
||||||
|
verbose_name_plural = _('Tierarten')
|
||||||
|
|
||||||
|
|
||||||
|
class RescueOrganization(models.Model):
|
||||||
name = models.CharField(max_length=200)
|
name = models.CharField(max_length=200)
|
||||||
trusted = models.BooleanField(default=False, verbose_name=_('Vertrauenswürdig'))
|
trusted = models.BooleanField(default=False, verbose_name=_('Vertrauenswürdig'))
|
||||||
allows_using_materials = models.CharField(max_length=200,
|
allows_using_materials = models.CharField(max_length=200,
|
||||||
@@ -149,10 +173,29 @@ 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'),
|
||||||
|
help_text=_(
|
||||||
|
"Es findet gerade Kommunikation zwischen Notfellchen und der Organisation statt."))
|
||||||
parent_org = models.ForeignKey("RescueOrganization", on_delete=models.PROTECT, blank=True, null=True)
|
parent_org = models.ForeignKey("RescueOrganization", on_delete=models.PROTECT, blank=True, null=True)
|
||||||
|
# allows to specify if a rescue organization has a specialization for dedicated species
|
||||||
|
specializations = models.ManyToManyField(Species, blank=True)
|
||||||
|
twenty_id = models.UUIDField(verbose_name=_("Twenty-ID"), null=True, blank=True,
|
||||||
|
help_text=_("ID der der Organisation in Twenty"))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('external_object_identifier', 'external_source_identifier',)
|
unique_together = ('external_object_identifier', 'external_source_identifier',)
|
||||||
|
ordering = ['name']
|
||||||
|
verbose_name = _("Tierschutzorganisation")
|
||||||
|
verbose_name_plural = _("Tierschutzorganisationen")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.name}"
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
super().clean()
|
super().clean()
|
||||||
@@ -166,6 +209,29 @@ class RescueOrganization(models.Model):
|
|||||||
def adoption_notices(self):
|
def adoption_notices(self):
|
||||||
return AdoptionNotice.objects.filter(organization=self)
|
return AdoptionNotice.objects.filter(organization=self)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def adoption_notices_in_hierarchy(self):
|
||||||
|
"""
|
||||||
|
Shows all adoption notices of this rescue organization and all child organizations.
|
||||||
|
"""
|
||||||
|
adoption_notices_discovered = list(self.adoption_notices)
|
||||||
|
if self.child_organizations:
|
||||||
|
for child in self.child_organizations:
|
||||||
|
adoption_notices_discovered.extend(child.adoption_notices_in_hierarchy)
|
||||||
|
return adoption_notices_discovered
|
||||||
|
|
||||||
|
@property
|
||||||
|
def adoption_notices_in_hierarchy_divided_by_status(self):
|
||||||
|
"""Returns two lists of adoption notices, the first active, the other inactive."""
|
||||||
|
active_adoption_notices = []
|
||||||
|
inactive_adoption_notices = []
|
||||||
|
for an in self.adoption_notices_in_hierarchy:
|
||||||
|
if an.is_active:
|
||||||
|
active_adoption_notices.append(an)
|
||||||
|
else:
|
||||||
|
inactive_adoption_notices.append(an)
|
||||||
|
return active_adoption_notices, inactive_adoption_notices
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def position(self):
|
def position(self):
|
||||||
if self.location:
|
if self.location:
|
||||||
@@ -202,9 +268,17 @@ class RescueOrganization(models.Model):
|
|||||||
"""
|
"""
|
||||||
return self.instagram or self.facebook or self.website or self.phone_number or self.email or self.fediverse_profile
|
return self.instagram or self.facebook or self.website or self.phone_number or self.email or self.fediverse_profile
|
||||||
|
|
||||||
def set_exclusion_from_checks(self):
|
@property
|
||||||
self.exclude_from_check = True
|
def child_organizations(self):
|
||||||
self.save()
|
return RescueOrganization.objects.filter(parent_org=self)
|
||||||
|
|
||||||
|
def in_distance(self, position, max_distance, unknown_true=True):
|
||||||
|
"""
|
||||||
|
Returns a boolean indicating if the Location of the adoption notice is within a given distance to the position
|
||||||
|
|
||||||
|
If the location is none, we by default return that the location is within the given distance
|
||||||
|
"""
|
||||||
|
return geo.object_in_distance(self, position, max_distance, unknown_true)
|
||||||
|
|
||||||
|
|
||||||
# Admins can perform all actions and have the highest trust associated with them
|
# Admins can perform all actions and have the highest trust associated with them
|
||||||
@@ -234,8 +308,7 @@ class User(AbstractUser):
|
|||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
organization_affiliation = models.ForeignKey(RescueOrganization, on_delete=models.PROTECT, null=True, blank=True,
|
organization_affiliation = models.ForeignKey(RescueOrganization, on_delete=models.PROTECT, null=True, blank=True,
|
||||||
verbose_name=_('Organisation'))
|
verbose_name=_('Organisation'))
|
||||||
reason_for_signup = models.TextField(verbose_name=_("Grund für die Registrierung"), help_text=_(
|
reason_for_signup = models.TextField(verbose_name=reason_for_signup_label, help_text=reason_for_signup_help_text)
|
||||||
"Wir würden gerne wissen warum du dich registriertst, ob du dich z.B. Tiere eines bestimmten Tierheim einstellen willst 'nur mal gucken' willst. Beides ist toll! Wenn du für ein Tierheim/eine Pflegestelle arbeitest kontaktieren wir dich ggf. um dir erweiterte Rechte zu geben."))
|
|
||||||
email_notifications = models.BooleanField(verbose_name=_("Benachrichtigung per E-Mail"), default=True)
|
email_notifications = models.BooleanField(verbose_name=_("Benachrichtigung per E-Mail"), default=True)
|
||||||
REQUIRED_FIELDS = ["reason_for_signup", "email"]
|
REQUIRED_FIELDS = ["reason_for_signup", "email"]
|
||||||
|
|
||||||
@@ -252,14 +325,17 @@ class User(AbstractUser):
|
|||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse("user-detail", args=[str(self.pk)])
|
return reverse("user-detail", args=[str(self.pk)])
|
||||||
|
|
||||||
|
def get_full_url(self):
|
||||||
|
return f"{base_url}{self.get_absolute_url()}"
|
||||||
|
|
||||||
def get_notifications_url(self):
|
def get_notifications_url(self):
|
||||||
return self.get_absolute_url()
|
return self.get_absolute_url()
|
||||||
|
|
||||||
def get_unread_notifications(self):
|
def get_unread_notifications(self):
|
||||||
return Notification.objects.filter(user=self, read=False)
|
return Notification.objects.filter(user_to_notify=self, read=False)
|
||||||
|
|
||||||
def get_num_unread_notifications(self):
|
def get_num_unread_notifications(self):
|
||||||
return Notification.objects.filter(user=self, read=False).count()
|
return Notification.objects.filter(user_to_notify=self, read=False).count()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def adoption_notices(self):
|
def adoption_notices(self):
|
||||||
@@ -271,8 +347,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)
|
||||||
@@ -280,25 +357,18 @@ class Image(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.alt_text
|
return self.alt_text
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Bild")
|
||||||
|
verbose_name_plural = _("Bilder")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
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
|
||||||
class Species(models.Model):
|
def as_base64(self):
|
||||||
"""Model representing a species of animal."""
|
encoded_string = base64.b64encode(self.image.file.read())
|
||||||
name = models.CharField(max_length=200, help_text=_('Name der Tierart'),
|
return encoded_string.decode("utf-8")
|
||||||
verbose_name=_('Name'))
|
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
"""String for representing the Model object."""
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('Tierart')
|
|
||||||
verbose_name_plural = _('Tierarten')
|
|
||||||
|
|
||||||
|
|
||||||
class AdoptionNotice(models.Model):
|
class AdoptionNotice(models.Model):
|
||||||
@@ -306,11 +376,11 @@ class AdoptionNotice(models.Model):
|
|||||||
permissions = [
|
permissions = [
|
||||||
("create_active_adoption_notice", "Can create an active adoption notice"),
|
("create_active_adoption_notice", "Can create an active adoption notice"),
|
||||||
]
|
]
|
||||||
|
verbose_name = _("Vermittlung")
|
||||||
|
verbose_name_plural = _("Vermittlungen")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if not hasattr(self, 'adoptionnoticestatus'):
|
|
||||||
return self.name
|
return self.name
|
||||||
return f"[{self.adoptionnoticestatus.as_string()}] {self.name}"
|
|
||||||
|
|
||||||
created_at = models.DateField(verbose_name=_('Erstellt am'), default=timezone.now)
|
created_at = models.DateField(verbose_name=_('Erstellt am'), default=timezone.now)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
@@ -330,6 +400,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):
|
||||||
@@ -344,11 +419,19 @@ class AdoptionNotice(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def num_per_sex(self):
|
def num_per_sex(self):
|
||||||
|
print(f"{self.pk} x")
|
||||||
num_per_sex = dict()
|
num_per_sex = dict()
|
||||||
for sex in SexChoices:
|
for sex in SexChoices:
|
||||||
num_per_sex[sex] = self.animals.filter(sex=sex).count
|
num_per_sex[sex] = len([animal for animal in self.animals if animal.sex == sex])
|
||||||
return num_per_sex
|
return num_per_sex
|
||||||
|
|
||||||
|
@property
|
||||||
|
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
|
||||||
@@ -378,12 +461,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."""
|
||||||
@@ -420,6 +512,7 @@ class AdoptionNotice(models.Model):
|
|||||||
photos.extend(animal.photos.all())
|
photos.extend(animal.photos.all())
|
||||||
if len(photos) > 0:
|
if len(photos) > 0:
|
||||||
return photos
|
return photos
|
||||||
|
return None
|
||||||
|
|
||||||
def get_photo(self):
|
def get_photo(self):
|
||||||
"""
|
"""
|
||||||
@@ -443,42 +536,36 @@ class AdoptionNotice(models.Model):
|
|||||||
|
|
||||||
If the location is none, we by default return that the location is within the given distance
|
If the location is none, we by default return that the location is within the given distance
|
||||||
"""
|
"""
|
||||||
if unknown_true and self.position is None:
|
return geo.object_in_distance(self, position, max_distance, unknown_true)
|
||||||
return True
|
|
||||||
|
|
||||||
distance = geo.calculate_distance_between_coordinates(self.position, position)
|
@staticmethod
|
||||||
return distance < max_distance
|
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_unchecked(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_unchecked
|
|
||||||
|
|
||||||
def set_closed(self):
|
@property
|
||||||
self.last_checked = timezone.now()
|
def is_closed(self):
|
||||||
self.save()
|
return self.adoption_notice_status in self._values_of(AdoptionNoticeStatusChoices.Closed.choices)
|
||||||
self.adoptionnoticestatus.set_closed()
|
|
||||||
|
|
||||||
def set_active(self):
|
@property
|
||||||
self.last_checked = timezone.now()
|
def is_awaiting_action(self):
|
||||||
self.save()
|
return self.adoption_notice_status in self._values_of(AdoptionNoticeStatusChoices.AwaitingAction.choices)
|
||||||
if not hasattr(self, 'adoptionnoticestatus'):
|
|
||||||
AdoptionNoticeStatus.create_other(self)
|
@property
|
||||||
self.adoptionnoticestatus.set_active()
|
def status_description(self):
|
||||||
|
return AdoptionNoticeStatusChoicesDescriptions.mapping[self.adoption_notice_status]
|
||||||
|
|
||||||
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}"
|
||||||
@@ -489,100 +576,13 @@ class AdoptionNotice(models.Model):
|
|||||||
text=text,
|
text=text,
|
||||||
title=notification_title)
|
title=notification_title)
|
||||||
|
|
||||||
|
def last_posted(self, platform=None):
|
||||||
class AdoptionNoticeStatus(models.Model):
|
if platform is None:
|
||||||
"""
|
last_post = SocialMediaPost.objects.filter(adoption_notice=self).order_by('-created_at').first()
|
||||||
The major status indicates a general state of an adoption notice
|
else:
|
||||||
whereas the minor status is used for reporting
|
last_post = SocialMediaPost.objects.filter(adoption_notice=self, platform=platform).order_by(
|
||||||
"""
|
'-created_at').first()
|
||||||
|
return last_post.created_at
|
||||||
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_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):
|
|
||||||
self.major_status = self.MAJOR_STATUS_CHOICES[self.CLOSED]
|
|
||||||
self.minor_status = self.MINOR_STATUS_CHOICES[self.CLOSED]["other"]
|
|
||||||
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):
|
||||||
@@ -603,14 +603,19 @@ class SexChoicesWithAll(models.TextChoices):
|
|||||||
|
|
||||||
|
|
||||||
class Animal(models.Model):
|
class Animal(models.Model):
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Tier')
|
||||||
|
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)
|
||||||
@@ -668,12 +673,17 @@ class SearchSubscription(models.Model):
|
|||||||
- On new AdoptionNotice: Check all existing SearchSubscriptions for matches
|
- On new AdoptionNotice: Check all existing SearchSubscriptions for matches
|
||||||
- For matches: Send notification to user of the SearchSubscription
|
- For matches: Send notification to user of the SearchSubscription
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Abonnierte Suche")
|
||||||
|
verbose_name_plural = _("Abonnierte Suchen")
|
||||||
|
|
||||||
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:
|
||||||
@@ -686,15 +696,24 @@ class Rule(models.Model):
|
|||||||
"""
|
"""
|
||||||
Class to store rules
|
Class to store rules
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Regel")
|
||||||
|
verbose_name_plural = _("Regeln")
|
||||||
|
|
||||||
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
|
||||||
@@ -702,7 +721,8 @@ class Rule(models.Model):
|
|||||||
|
|
||||||
class Report(models.Model):
|
class Report(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
permissions = []
|
verbose_name = _("Meldung")
|
||||||
|
verbose_name_plural = _("Meldungen")
|
||||||
|
|
||||||
ACTION_TAKEN = "action taken"
|
ACTION_TAKEN = "action taken"
|
||||||
NO_ACTION_TAKEN = "no action taken"
|
NO_ACTION_TAKEN = "no action taken"
|
||||||
@@ -717,8 +737,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}"
|
||||||
@@ -727,6 +747,9 @@ class Report(models.Model):
|
|||||||
"""Returns the url to access a detailed page for the report."""
|
"""Returns the url to access a detailed page for the report."""
|
||||||
return reverse('report-detail', args=[str(self.id)])
|
return reverse('report-detail', args=[str(self.id)])
|
||||||
|
|
||||||
|
def get_full_url(self):
|
||||||
|
return f"{base_url}{self.get_absolute_url()}"
|
||||||
|
|
||||||
def get_reported_rules(self):
|
def get_reported_rules(self):
|
||||||
return self.reported_broken_rules.all()
|
return self.reported_broken_rules.all()
|
||||||
|
|
||||||
@@ -779,6 +802,10 @@ class ReportComment(Report):
|
|||||||
|
|
||||||
|
|
||||||
class ModerationAction(models.Model):
|
class ModerationAction(models.Model):
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Moderationsaktion")
|
||||||
|
verbose_name_plural = _("Moderationsaktionen")
|
||||||
|
|
||||||
BAN = "user_banned"
|
BAN = "user_banned"
|
||||||
DELETE = "content_deleted"
|
DELETE = "content_deleted"
|
||||||
COMMENT = "comment"
|
COMMENT = "comment"
|
||||||
@@ -815,7 +842,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)
|
||||||
@@ -843,6 +870,11 @@ class Announcement(Text):
|
|||||||
"""
|
"""
|
||||||
Class to store announcements that should be displayed for all users
|
Class to store announcements that should be displayed for all users
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Banner")
|
||||||
|
verbose_name_plural = _("Banner")
|
||||||
|
|
||||||
logged_in_only = models.BooleanField(default=False)
|
logged_in_only = models.BooleanField(default=False)
|
||||||
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)
|
||||||
@@ -892,10 +924,15 @@ class Comment(models.Model):
|
|||||||
"""
|
"""
|
||||||
Class to store comments in markdown content
|
Class to store comments in markdown content
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Kommentar")
|
||||||
|
verbose_name_plural = _("Kommentare")
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
@@ -910,33 +947,28 @@ class Comment(models.Model):
|
|||||||
return self.adoption_notice.get_absolute_url()
|
return self.adoption_notice.get_absolute_url()
|
||||||
|
|
||||||
|
|
||||||
class NotificationTypeChoices(models.TextChoices):
|
|
||||||
NEW_USER = "new_user", _("Useraccount wurde erstellt")
|
|
||||||
NEW_REPORT_AN = "new_report_an", _("Vermittlung wurde gemeldet")
|
|
||||||
NEW_REPORT_COMMENT = "new_report_comment", _("Kommentar wurde gemeldet")
|
|
||||||
AN_IS_TO_BE_CHECKED = "an_is_to_be_checked", _("Vermittlung muss überprüft werden")
|
|
||||||
AN_WAS_DEACTIVATED = "an_was_deactivated", _("Vermittlung wurde deaktiviert")
|
|
||||||
AN_FOR_SEARCH_FOUND = "an_for_search_found", _("Vermittlung für Suche gefunden")
|
|
||||||
NEW_COMMENT = "new_comment", _("Neuer Kommentar")
|
|
||||||
|
|
||||||
|
|
||||||
class Notification(models.Model):
|
class Notification(models.Model):
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Benachrichtigung")
|
||||||
|
verbose_name_plural = _("Benachrichtigungen")
|
||||||
|
|
||||||
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)
|
||||||
read_at = models.DateTimeField(blank=True, null=True, verbose_name=_("Gelesen am"))
|
|
||||||
notification_type = models.CharField(max_length=200,
|
notification_type = models.CharField(max_length=200,
|
||||||
choices=NotificationTypeChoices.choices,
|
choices=NotificationTypeChoices.choices,
|
||||||
verbose_name=_('Benachrichtigungsgrund'))
|
verbose_name=_('Benachrichtigungsgrund'))
|
||||||
title = models.CharField(max_length=100, verbose_name=_("Titel"))
|
|
||||||
text = models.TextField(verbose_name="Inhalt")
|
|
||||||
user_to_notify = models.ForeignKey(User,
|
user_to_notify = models.ForeignKey(User,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
verbose_name=_('Nutzer*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"))
|
||||||
|
text = models.TextField(verbose_name="Inhalt")
|
||||||
read = models.BooleanField(default=False)
|
read = models.BooleanField(default=False)
|
||||||
|
read_at = models.DateTimeField(blank=True, null=True, verbose_name=_("Gelesen am"))
|
||||||
comment = models.ForeignKey(Comment, blank=True, null=True, on_delete=models.CASCADE, verbose_name=_('Antwort'))
|
comment = models.ForeignKey(Comment, blank=True, null=True, on_delete=models.CASCADE, verbose_name=_('Antwort'))
|
||||||
adoption_notice = models.ForeignKey(AdoptionNotice, blank=True, null=True, on_delete=models.CASCADE, verbose_name=_('Vermittlung'))
|
adoption_notice = models.ForeignKey(AdoptionNotice, blank=True, null=True, on_delete=models.CASCADE,
|
||||||
|
verbose_name=_('Vermittlung'))
|
||||||
user_related = models.ForeignKey(User,
|
user_related = models.ForeignKey(User,
|
||||||
blank=True, null=True,
|
blank=True, null=True,
|
||||||
on_delete=models.CASCADE, verbose_name=_('Verwandter Useraccount'),
|
on_delete=models.CASCADE, verbose_name=_('Verwandter Useraccount'),
|
||||||
@@ -958,10 +990,20 @@ class Notification(models.Model):
|
|||||||
self.read_at = timezone.now()
|
self.read_at = timezone.now()
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
def get_body_part(self):
|
||||||
|
return NotificationDisplayMapping[self.notification_type].web_partial
|
||||||
|
|
||||||
|
|
||||||
class Subscriptions(models.Model):
|
class Subscriptions(models.Model):
|
||||||
|
"""Subscription to a AdoptionNotice"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Abonnement")
|
||||||
|
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)
|
||||||
|
|
||||||
@@ -987,6 +1029,11 @@ class Timestamp(models.Model):
|
|||||||
"""
|
"""
|
||||||
Class to store timestamps based on keys
|
Class to store timestamps based on keys
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Zeitstempel")
|
||||||
|
verbose_name_plural = _("Zeitstempel")
|
||||||
|
|
||||||
key = models.CharField(max_length=255, verbose_name=_("Schlüssel"), primary_key=True)
|
key = models.CharField(max_length=255, verbose_name=_("Schlüssel"), primary_key=True)
|
||||||
timestamp = models.DateTimeField(auto_now_add=True, verbose_name=_("Zeitstempel"))
|
timestamp = models.DateTimeField(auto_now_add=True, verbose_name=_("Zeitstempel"))
|
||||||
data = models.CharField(max_length=2000, blank=True, null=True)
|
data = models.CharField(max_length=2000, blank=True, null=True)
|
||||||
@@ -999,19 +1046,33 @@ class SpeciesSpecificURL(models.Model):
|
|||||||
"""
|
"""
|
||||||
Model that allows to specify a URL for a rescue organization where a certain species can be found
|
Model that allows to specify a URL for a rescue organization where a certain species can be found
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Tierartspezifische URL")
|
||||||
|
verbose_name_plural = _("Tierartspezifische URLs")
|
||||||
|
|
||||||
species = models.ForeignKey(Species, on_delete=models.CASCADE, verbose_name=_("Tierart"))
|
species = models.ForeignKey(Species, on_delete=models.CASCADE, verbose_name=_("Tierart"))
|
||||||
rescue_organization = models.ForeignKey(RescueOrganization, on_delete=models.CASCADE,
|
rescue_organization = models.ForeignKey(RescueOrganization, on_delete=models.CASCADE,
|
||||||
verbose_name=_("Tierschutzorganisation"))
|
verbose_name=_("Tierschutzorganisation"))
|
||||||
url = models.URLField(verbose_name=_("Tierartspezifische URL"))
|
url = models.URLField(verbose_name=_("Tierartspezifische URL"))
|
||||||
|
|
||||||
|
|
||||||
class SpeciesSpecialization(models.Model):
|
class PlatformChoices(models.TextChoices):
|
||||||
"""
|
FEDIVERSE = "fediverse", _("Fediverse")
|
||||||
Model that allows to specify if a rescue organization has a specialization for dedicated species
|
|
||||||
"""
|
|
||||||
species = models.ForeignKey(Species, on_delete=models.CASCADE, verbose_name=_("Tierart"))
|
class SocialMediaPost(models.Model):
|
||||||
rescue_organization = models.ForeignKey(RescueOrganization, on_delete=models.CASCADE,
|
created_at = models.DateField(verbose_name=_('Erstellt am'), default=timezone.now)
|
||||||
verbose_name=_("Tierschutzorganisation"))
|
platform = models.CharField(max_length=255, verbose_name=_("Social Media Platform"),
|
||||||
|
choices=PlatformChoices.choices)
|
||||||
|
adoption_notice = models.ForeignKey(AdoptionNotice, on_delete=models.CASCADE, verbose_name=_('Vermittlung'))
|
||||||
|
url = models.URLField(verbose_name=_("URL"))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_an_to_post():
|
||||||
|
adoption_notices_without_post = AdoptionNotice.objects.filter(socialmediapost__isnull=True,
|
||||||
|
adoption_notice_status__in=AdoptionNoticeStatusChoices.Active.values)
|
||||||
|
return adoption_notices_without_post.first()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{_('Spezialisierung')} {self.species}"
|
return f"{self.platform} - {self.adoption_notice}"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from django.contrib.sitemaps import Sitemap
|
from django.contrib.sitemaps import Sitemap
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from .models import AdoptionNotice, RescueOrganization
|
from .models import AdoptionNotice, RescueOrganization, ImportantLocation, Animal
|
||||||
|
|
||||||
|
|
||||||
class StaticViewSitemap(Sitemap):
|
class StaticViewSitemap(Sitemap):
|
||||||
@@ -8,7 +8,8 @@ class StaticViewSitemap(Sitemap):
|
|||||||
changefreq = "weekly"
|
changefreq = "weekly"
|
||||||
|
|
||||||
def items(self):
|
def items(self):
|
||||||
return ["index", "search", "map", "about", "rescue-organizations"]
|
return ["index", "search", "map", "about", "rescue-organizations", "buying", "imprint", "terms-of-service",
|
||||||
|
"privacy"]
|
||||||
|
|
||||||
def location(self, item):
|
def location(self, item):
|
||||||
return reverse(item)
|
return reverse(item)
|
||||||
@@ -25,17 +26,6 @@ class AdoptionNoticeSitemap(Sitemap):
|
|||||||
return obj.updated_at
|
return obj.updated_at
|
||||||
|
|
||||||
|
|
||||||
class AnimalSitemap(Sitemap):
|
|
||||||
priority = 0.2
|
|
||||||
changefreq = "daily"
|
|
||||||
|
|
||||||
def items(self):
|
|
||||||
return AdoptionNotice.objects.all()
|
|
||||||
|
|
||||||
def lastmod(self, obj):
|
|
||||||
return obj.updated_at
|
|
||||||
|
|
||||||
|
|
||||||
class RescueOrganizationSitemap(Sitemap):
|
class RescueOrganizationSitemap(Sitemap):
|
||||||
priority = 0.3
|
priority = 0.3
|
||||||
changefreq = "weekly"
|
changefreq = "weekly"
|
||||||
@@ -45,3 +35,11 @@ class RescueOrganizationSitemap(Sitemap):
|
|||||||
|
|
||||||
def lastmod(self, obj):
|
def lastmod(self, obj):
|
||||||
return obj.updated_at
|
return obj.updated_at
|
||||||
|
|
||||||
|
|
||||||
|
class SearchSitemap(Sitemap):
|
||||||
|
priority = 0.5
|
||||||
|
chanfreq = "daily"
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
return ImportantLocation.objects.all()
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -320,3 +321,43 @@ AN Cards
|
|||||||
background-color: var(--bulma-success-on-scheme);
|
background-color: var(--bulma-success-on-scheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.notification-container {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-label {
|
||||||
|
padding: 2px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make the badge float in the top right corner of the button */
|
||||||
|
.notification-badge {
|
||||||
|
background-color: #fa3e3e;
|
||||||
|
border-radius: 2px;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
padding: 1px 3px;
|
||||||
|
font-size: 8px;
|
||||||
|
|
||||||
|
position: absolute; /* Position the badge within the relatively positioned button */
|
||||||
|
top: 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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
11
src/fellchensammlung/static/fellchensammlung/js/mousetrap.min.js
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/* mousetrap v1.6.5 craig.is/killing/mice */
|
||||||
|
(function(q,u,c){function v(a,b,g){a.addEventListener?a.addEventListener(b,g,!1):a.attachEvent("on"+b,g)}function z(a){if("keypress"==a.type){var b=String.fromCharCode(a.which);a.shiftKey||(b=b.toLowerCase());return b}return n[a.which]?n[a.which]:r[a.which]?r[a.which]:String.fromCharCode(a.which).toLowerCase()}function F(a){var b=[];a.shiftKey&&b.push("shift");a.altKey&&b.push("alt");a.ctrlKey&&b.push("ctrl");a.metaKey&&b.push("meta");return b}function w(a){return"shift"==a||"ctrl"==a||"alt"==a||
|
||||||
|
"meta"==a}function A(a,b){var g,d=[];var e=a;"+"===e?e=["+"]:(e=e.replace(/\+{2}/g,"+plus"),e=e.split("+"));for(g=0;g<e.length;++g){var m=e[g];B[m]&&(m=B[m]);b&&"keypress"!=b&&C[m]&&(m=C[m],d.push("shift"));w(m)&&d.push(m)}e=m;g=b;if(!g){if(!p){p={};for(var c in n)95<c&&112>c||n.hasOwnProperty(c)&&(p[n[c]]=c)}g=p[e]?"keydown":"keypress"}"keypress"==g&&d.length&&(g="keydown");return{key:m,modifiers:d,action:g}}function D(a,b){return null===a||a===u?!1:a===b?!0:D(a.parentNode,b)}function d(a){function b(a){a=
|
||||||
|
a||{};var b=!1,l;for(l in p)a[l]?b=!0:p[l]=0;b||(x=!1)}function g(a,b,t,f,g,d){var l,E=[],h=t.type;if(!k._callbacks[a])return[];"keyup"==h&&w(a)&&(b=[a]);for(l=0;l<k._callbacks[a].length;++l){var c=k._callbacks[a][l];if((f||!c.seq||p[c.seq]==c.level)&&h==c.action){var e;(e="keypress"==h&&!t.metaKey&&!t.ctrlKey)||(e=c.modifiers,e=b.sort().join(",")===e.sort().join(","));e&&(e=f&&c.seq==f&&c.level==d,(!f&&c.combo==g||e)&&k._callbacks[a].splice(l,1),E.push(c))}}return E}function c(a,b,c,f){k.stopCallback(b,
|
||||||
|
b.target||b.srcElement,c,f)||!1!==a(b,c)||(b.preventDefault?b.preventDefault():b.returnValue=!1,b.stopPropagation?b.stopPropagation():b.cancelBubble=!0)}function e(a){"number"!==typeof a.which&&(a.which=a.keyCode);var b=z(a);b&&("keyup"==a.type&&y===b?y=!1:k.handleKey(b,F(a),a))}function m(a,g,t,f){function h(c){return function(){x=c;++p[a];clearTimeout(q);q=setTimeout(b,1E3)}}function l(g){c(t,g,a);"keyup"!==f&&(y=z(g));setTimeout(b,10)}for(var d=p[a]=0;d<g.length;++d){var e=d+1===g.length?l:h(f||
|
||||||
|
A(g[d+1]).action);n(g[d],e,f,a,d)}}function n(a,b,c,f,d){k._directMap[a+":"+c]=b;a=a.replace(/\s+/g," ");var e=a.split(" ");1<e.length?m(a,e,b,c):(c=A(a,c),k._callbacks[c.key]=k._callbacks[c.key]||[],g(c.key,c.modifiers,{type:c.action},f,a,d),k._callbacks[c.key][f?"unshift":"push"]({callback:b,modifiers:c.modifiers,action:c.action,seq:f,level:d,combo:a}))}var k=this;a=a||u;if(!(k instanceof d))return new d(a);k.target=a;k._callbacks={};k._directMap={};var p={},q,y=!1,r=!1,x=!1;k._handleKey=function(a,
|
||||||
|
d,e){var f=g(a,d,e),h;d={};var k=0,l=!1;for(h=0;h<f.length;++h)f[h].seq&&(k=Math.max(k,f[h].level));for(h=0;h<f.length;++h)f[h].seq?f[h].level==k&&(l=!0,d[f[h].seq]=1,c(f[h].callback,e,f[h].combo,f[h].seq)):l||c(f[h].callback,e,f[h].combo);f="keypress"==e.type&&r;e.type!=x||w(a)||f||b(d);r=l&&"keydown"==e.type};k._bindMultiple=function(a,b,c){for(var d=0;d<a.length;++d)n(a[d],b,c)};v(a,"keypress",e);v(a,"keydown",e);v(a,"keyup",e)}if(q){var n={8:"backspace",9:"tab",13:"enter",16:"shift",17:"ctrl",
|
||||||
|
18:"alt",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"ins",46:"del",91:"meta",93:"meta",224:"meta"},r={106:"*",107:"+",109:"-",110:".",111:"/",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},C={"~":"`","!":"1","@":"2","#":"3",$:"4","%":"5","^":"6","&":"7","*":"8","(":"9",")":"0",_:"-","+":"=",":":";",'"':"'","<":",",">":".","?":"/","|":"\\"},B={option:"alt",command:"meta","return":"enter",
|
||||||
|
escape:"esc",plus:"+",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},p;for(c=1;20>c;++c)n[111+c]="f"+c;for(c=0;9>=c;++c)n[c+96]=c.toString();d.prototype.bind=function(a,b,c){a=a instanceof Array?a:[a];this._bindMultiple.call(this,a,b,c);return this};d.prototype.unbind=function(a,b){return this.bind.call(this,a,function(){},b)};d.prototype.trigger=function(a,b){if(this._directMap[a+":"+b])this._directMap[a+":"+b]({},a);return this};d.prototype.reset=function(){this._callbacks={};
|
||||||
|
this._directMap={};return this};d.prototype.stopCallback=function(a,b){if(-1<(" "+b.className+" ").indexOf(" mousetrap ")||D(b,this.target))return!1;if("composedPath"in a&&"function"===typeof a.composedPath){var c=a.composedPath()[0];c!==a.target&&(b=c)}return"INPUT"==b.tagName||"SELECT"==b.tagName||"TEXTAREA"==b.tagName||b.isContentEditable};d.prototype.handleKey=function(){return this._handleKey.apply(this,arguments)};d.addKeycodes=function(a){for(var b in a)a.hasOwnProperty(b)&&(n[b]=a[b]);p=null};
|
||||||
|
d.init=function(){var a=d(u),b;for(b in a)"_"!==b.charAt(0)&&(d[b]=function(b){return function(){return a[b].apply(a,arguments)}}(b))};d.init();q.Mousetrap=d;"undefined"!==typeof module&&module.exports&&(module.exports=d);"function"===typeof define&&define.amd&&define(function(){return d})}})("undefined"!==typeof window?window:null,"undefined"!==typeof window?document:null);
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
function mark_checked(index) {
|
||||||
|
document.getElementById('mark_checked_'+index).submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
function open_information(index) {
|
||||||
|
let link = document.getElementById('species_url_'+index+'_1');
|
||||||
|
if (!link) {
|
||||||
|
link = document.getElementById('rescue_org_website_'+index);
|
||||||
|
}
|
||||||
|
window.open(link.href);
|
||||||
|
}
|
||||||
|
|
||||||
|
Mousetrap.bind('c', function() { mark_checked(1); });
|
||||||
|
|
||||||
|
Mousetrap.bind('o', function() { open_information(1); });
|
||||||
@@ -22,7 +22,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
// Looks for all notifications with a delete and allows closing them when pressing delete
|
// Looks for all notifications with a delete and allows closing them when pressing delete
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
(document.querySelectorAll('.notification .delete') || []).forEach(($delete) => {
|
(document.querySelectorAll('.notification .delete:not(.js-delete-excluded)') || []).forEach(($delete) => {
|
||||||
const $notification = $delete.parentNode;
|
const $notification = $delete.parentNode;
|
||||||
|
|
||||||
$delete.addEventListener('click', () => {
|
$delete.addEventListener('click', () => {
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ from django.utils import timezone
|
|||||||
from notfellchen.celery import app as celery_app
|
from notfellchen.celery import app as celery_app
|
||||||
from .mail import send_notification_email
|
from .mail import send_notification_email
|
||||||
from .tools.admin import clean_locations, deactivate_unchecked_adoption_notices, deactivate_404_adoption_notices
|
from .tools.admin import clean_locations, deactivate_unchecked_adoption_notices, deactivate_404_adoption_notices
|
||||||
|
from .tools.fedi import post_an_to_fedi
|
||||||
from .tools.misc import healthcheck_ok
|
from .tools.misc import healthcheck_ok
|
||||||
from .models import Location, AdoptionNotice, Timestamp, RescueOrganization
|
from .models import Location, AdoptionNotice, Timestamp, RescueOrganization, SocialMediaPost
|
||||||
from .tools.notifications import notify_of_AN_to_be_checked
|
from .tools.notifications import notify_of_AN_to_be_checked
|
||||||
from .tools.search import notify_search_subscribers
|
from .tools.search import notify_search_subscribers
|
||||||
|
|
||||||
@@ -38,6 +39,14 @@ def task_deactivate_unchecked():
|
|||||||
set_timestamp("task_deactivate_404_adoption_notices")
|
set_timestamp("task_deactivate_404_adoption_notices")
|
||||||
|
|
||||||
|
|
||||||
|
@celery_app.task(name="social_media.post_fedi")
|
||||||
|
def task_post_to_fedi():
|
||||||
|
adoption_notice = SocialMediaPost.get_an_to_post()
|
||||||
|
if adoption_notice is not None:
|
||||||
|
post_an_to_fedi(adoption_notice)
|
||||||
|
set_timestamp("task_social_media.post_fedi")
|
||||||
|
|
||||||
|
|
||||||
@celery_app.task(name="commit.post_an_save")
|
@celery_app.task(name="commit.post_an_save")
|
||||||
def post_adoption_notice_save(pk):
|
def post_adoption_notice_save(pk):
|
||||||
instance = AdoptionNotice.objects.get(pk=pk)
|
instance = AdoptionNotice.objects.get(pk=pk)
|
||||||
|
|||||||
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" %}
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h2 class="card-header-title">{{ faq.title }}</h2>
|
<h2 class="card-header-title">{{ faq.title }}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-content">
|
<div class="card-content content">
|
||||||
{{ faq.content | render_markdown }}
|
{{ faq.content | render_markdown }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,11 +16,27 @@
|
|||||||
{% block canonical_url %}{% host %}{% url 'rescue-organizations' %}{% endblock %}
|
{% block canonical_url %}{% host %}{% url 'rescue-organizations' %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="block">
|
<div class="columns block">
|
||||||
|
<div class=" column {% if show_search %}is-two-thirds {% endif %}">
|
||||||
<div style="height: 70vh">
|
<div style="height: 70vh">
|
||||||
{% include "fellchensammlung/partials/partial-map.html" %}
|
{% include "fellchensammlung/partials/partial-map.html" %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% if show_search %}
|
||||||
|
<div class="column is-one-third">
|
||||||
|
<form method="GET" autocomplete="off">
|
||||||
|
<input type="hidden" name="longitude" maxlength="200" id="longitude">
|
||||||
|
<input type="hidden" name="latitude" maxlength="200" id="latitude">
|
||||||
|
<!--- https://docs.djangoproject.com/en/5.2/topics/forms/#reusable-form-templates -->
|
||||||
|
{{ search_form }}
|
||||||
|
<button class="button is-primary is-fullwidth" type="submit" value="search" name="action">
|
||||||
|
<i class="fas fa-search fa-fw"></i> {% trans 'Suchen' %}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="block">
|
<div class="block">
|
||||||
{% with rescue_organizations=rescue_organizations_to_list %}
|
{% with rescue_organizations=rescue_organizations_to_list %}
|
||||||
{% include "fellchensammlung/lists/list-animal-shelters.html" %}
|
{% include "fellchensammlung/lists/list-animal-shelters.html" %}
|
||||||
@@ -29,16 +45,17 @@
|
|||||||
<nav class="pagination" role="navigation" aria-label="{% trans 'Paginierung' %}">
|
<nav class="pagination" role="navigation" aria-label="{% trans 'Paginierung' %}">
|
||||||
{% if rescue_organizations_to_list.has_previous %}
|
{% if rescue_organizations_to_list.has_previous %}
|
||||||
<a class="pagination-previous"
|
<a class="pagination-previous"
|
||||||
href="?page={{ rescue_organizations_to_list.previous_page_number }}">{% trans 'Vorherige' %}</a>
|
href="?page={% url_replace request 'page' rescue_organizations_to_list.previous_page_number %}">{% trans 'Vorherige' %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if rescue_organizations_to_list.has_next %}
|
{% if rescue_organizations_to_list.has_next %}
|
||||||
<a class="pagination-next" href="?page={{ rescue_organizations_to_list.next_page_number }}">{% trans 'Nächste' %}</a>
|
<a class="pagination-next"
|
||||||
|
href="?{% url_replace request 'page' rescue_organizations_to_list.next_page_number %}">{% trans 'Nächste' %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<ul class="pagination-list">
|
<ul class="pagination-list">
|
||||||
{% for page in elided_page_range %}
|
{% for page in elided_page_range %}
|
||||||
{% if page != "…" %}
|
{% if page != "…" %}
|
||||||
<li>
|
<li>
|
||||||
<a href="?page={{ page }}"
|
<a href="?{% url_replace request 'page' page %}"
|
||||||
class="pagination-link {% if page == rescue_organizations_to_list.number %} is-current{% endif %}"
|
class="pagination-link {% if page == rescue_organizations_to_list.number %} is-current{% endif %}"
|
||||||
aria-label="{% blocktranslate %}Gehe zu Seite {{ page }}.{% endblocktranslate %}">
|
aria-label="{% blocktranslate %}Gehe zu Seite {{ page }}.{% endblocktranslate %}">
|
||||||
{{ page }}
|
{{ page }}
|
||||||
|
|||||||
@@ -37,6 +37,26 @@
|
|||||||
{% block header %}
|
{% block header %}
|
||||||
{% include "fellchensammlung/header.html" %}
|
{% include "fellchensammlung/header.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
{% if profile %}
|
||||||
|
<div class="profile">
|
||||||
|
<table class="table is-bordered is-fullwidth is-hoverable is-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>Timestamp</td>
|
||||||
|
<td>Status</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for status in profile %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ status.0 }}</td>
|
||||||
|
<td>{{ status.1 }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="main-content">
|
<div class="main-content">
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
@@ -45,5 +65,8 @@
|
|||||||
{% block footer %}
|
{% block footer %}
|
||||||
{% include "fellchensammlung/footer.html" %}
|
{% include "fellchensammlung/footer.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_body %}
|
||||||
|
{% endblock extra_body %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{% extends "fellchensammlung/base.html" %}
|
{% extends "fellchensammlung/base.html" %}
|
||||||
{% load custom_tags %}
|
{% load custom_tags %}
|
||||||
|
{% load admin_urls %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
@@ -25,18 +26,14 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="columns">
|
{% include "fellchensammlung/partials/partial-adoption-notice-status.html" %}
|
||||||
<div class="column is-two-thirds">
|
|
||||||
<!--- Title level (including action dropdown) -->
|
<!--- Title level (including action dropdown) -->
|
||||||
<div class="level">
|
<div class="block is-flex is-justify-content-space-between">
|
||||||
<div class="level-left">
|
<div class="">
|
||||||
<div class="level-item">
|
<h2 class="title is-3 is-size-4-mobile">{{ adoption_notice.name }}</h2>
|
||||||
<p class="title is-3 is-size-4-mobile">{{ adoption_notice.name }}</p>
|
|
||||||
</div>
|
|
||||||
</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">
|
||||||
@@ -49,30 +46,58 @@
|
|||||||
<!--- Action menu (dropdown) --->
|
<!--- Action menu (dropdown) --->
|
||||||
<div class="dropdown-menu" role="menu">
|
<div class="dropdown-menu" role="menu">
|
||||||
<div class="dropdown-content">
|
<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>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<form class="dropdown-item" method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="action" value="subscribe">
|
||||||
|
|
||||||
|
<button type="submit" id="submit">
|
||||||
|
<i class="fas fa-bell fa-fw"
|
||||||
|
aria-hidden="true"></i> {% trans 'Abonnieren' %}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
<hr class="dropdown-divider">
|
||||||
|
|
||||||
{% if has_edit_permission %}
|
{% if has_edit_permission %}
|
||||||
|
<form class="dropdown-item" method="POST">
|
||||||
<a class="dropdown-item">
|
{% csrf_token %}
|
||||||
<i class="fas fa-check"
|
<input type="hidden" name="action" value="checked_active">
|
||||||
|
<button type="submit" id="submit">
|
||||||
|
<i class="fas fa-check fa-fw"
|
||||||
aria-hidden="true"></i> {% trans 'Als aktiv bestätigen' %}
|
aria-hidden="true"></i> {% trans 'Als aktiv bestätigen' %}
|
||||||
</a>
|
</button>
|
||||||
|
</form>
|
||||||
<a class="dropdown-item"
|
<a class="dropdown-item"
|
||||||
href="{% url 'adoption-notice-edit' adoption_notice_id=adoption_notice.pk %}">
|
href="{% url 'adoption-notice-edit' adoption_notice_id=adoption_notice.pk %}">
|
||||||
<i class="fas fa-pencil"
|
<i class="fas fa-pencil fa-fw"
|
||||||
aria-hidden="true"></i> {% translate 'Bearbeiten' %}
|
aria-hidden="true"></i> {% translate 'Bearbeiten' %}
|
||||||
</a>
|
</a>
|
||||||
<a class="dropdown-item"
|
<a class="dropdown-item"
|
||||||
href="{% url 'adoption-notice-add-photo' adoption_notice.pk %}">
|
href="{% url 'adoption-notice-add-photo' adoption_notice.pk %}">
|
||||||
<i class="fas fa-image"
|
<i class="fas fa-image fa-fw"
|
||||||
aria-hidden="true"></i> {% trans 'Bilder hinzufügen' %}
|
aria-hidden="true"></i> {% trans 'Bilder 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-add-animal' adoption_notice.pk %}">
|
||||||
<i class="fas fa-plus"
|
<i class="fas fa-plus fa-fw"
|
||||||
aria-hidden="true"></i> {% trans 'Tier hinzufügen' %}
|
aria-hidden="true"></i> {% trans 'Tier hinzufügen' %}
|
||||||
</a>
|
</a>
|
||||||
<a class="dropdown-item">
|
<a class="dropdown-item"
|
||||||
<i class="fas fa-circle-xmark"
|
href="{% url 'adoption-notice-close' adoption_notice_id=adoption_notice.pk %}">
|
||||||
|
<i class="fas fa-circle-xmark fa-fw"
|
||||||
aria-hidden="true"></i> {% trans 'Deaktivieren' %}
|
aria-hidden="true"></i> {% trans 'Deaktivieren' %}
|
||||||
</a>
|
</a>
|
||||||
<hr class="dropdown-divider">
|
<hr class="dropdown-divider">
|
||||||
@@ -81,12 +106,27 @@
|
|||||||
<i class="fas fa-flag"
|
<i class="fas fa-flag"
|
||||||
aria-hidden="true"></i> {% trans 'Melden' %}
|
aria-hidden="true"></i> {% trans 'Melden' %}
|
||||||
</a>
|
</a>
|
||||||
|
{% if request.user.is_superuser %}
|
||||||
|
{% if oxitraffic_base_url %}
|
||||||
|
<hr class="dropdown-divider">
|
||||||
|
<a class="dropdown-item"
|
||||||
|
href="{{ oxitraffic_base_url }}/stats?path={{ adoption_notice.get_absolute_url }}">
|
||||||
|
<i class="fas fa-chart-line fa-fw"></i> {% trans 'Aufrufe' %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
<hr class="dropdown-divider">
|
||||||
|
<a class="dropdown-item is-warning"
|
||||||
|
href="{% url adoption_notice_meta|admin_urlname:'change' adoption_notice.pk %}">
|
||||||
|
<i class="fa-solid fa-tools fa-fw"></i> Admin interface
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</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">
|
||||||
@@ -119,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">
|
||||||
@@ -160,25 +200,36 @@
|
|||||||
<div class="column block">
|
<div class="column block">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h1 class="card-header-title title is-4">{% translate "Beschreibung" %}</h1>
|
<h4 class="card-header-title title is-4">{% translate "Beschreibung" %}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-content">
|
<div class="card-content content">
|
||||||
<p class="expandable">{% if adoption_notice.description %}
|
<p class="expandable">{% if adoption_notice.description %}
|
||||||
{{ adoption_notice.description | render_markdown }}
|
{{ adoption_notice.description | render_markdown }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% translate "Keine Beschreibung angegeben" %}
|
{% translate "Keine Beschreibung angegeben" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
|
<hr>
|
||||||
|
<p>
|
||||||
|
<strong>
|
||||||
|
{% translate 'Zuletzt auf Aktualität überprüft:' %}
|
||||||
|
</strong>
|
||||||
|
{{ adoption_notice.last_checked|time_since_hr }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</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 }}
|
||||||
|
<i
|
||||||
class="fa-solid fa-arrow-up-right-from-square fa-fw"></i>
|
class="fa-solid fa-arrow-up-right-from-square fa-fw"></i>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -193,7 +244,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
|
||||||
<div class="block">
|
<div class="block">
|
||||||
|
|||||||
@@ -19,30 +19,15 @@
|
|||||||
<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 %}
|
|
||||||
<p>{{ org.description | render_markdown }}</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</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 %}"><i class="fa-solid fa-tools fa-fw"></i> Admin interface</a>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
@@ -50,15 +35,43 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if org.child_organizations %}
|
||||||
|
<div class="block">
|
||||||
|
<h2 class="title is-2">{% translate 'Unterorganisationen' %}</h2>
|
||||||
|
{% with rescue_organizations=org.child_organizations %}
|
||||||
|
{% include "fellchensammlung/lists/list-animal-shelters.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
<h2 class="title is-2">{% translate 'Vermittlungen der Organisation' %}</h2>
|
<h2 class="title is-2">{% translate 'Vermittlungen der Organisation' %}</h2>
|
||||||
|
{% with ans_by_status=org.adoption_notices_in_hierarchy_divided_by_status %}
|
||||||
|
{% with active_ans=ans_by_status.0 inactive_ans=ans_by_status.1 %}
|
||||||
|
<div class="block">
|
||||||
|
<h3 class="title is-3">{% translate 'Aktive Vermittlungen' %}</h3>
|
||||||
<div class="container-cards">
|
<div class="container-cards">
|
||||||
{% if org.adoption_notices %}
|
{% if active_ans %}
|
||||||
{% for adoption_notice in org.adoption_notices %}
|
{% for adoption_notice in active_ans %}
|
||||||
{% include "fellchensammlung/partials/partial-adoption-notice-minimal.html" %}
|
{% include "fellchensammlung/partials/partial-adoption-notice-minimal.html" %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>{% translate "Keine Vermittlungen gefunden." %}</p>
|
<p>{% translate "Keine Vermittlungen gefunden." %}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="block">
|
||||||
|
<h3 class="title is-3">{% translate 'Inaktive Vermittlungen' %}</h3>
|
||||||
|
<div class="container-cards">
|
||||||
|
{% if inactive_ans %}
|
||||||
|
{% for adoption_notice in inactive_ans %}
|
||||||
|
{% include "fellchensammlung/partials/partial-adoption-notice-minimal.html" %}
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<p>{% translate "Keine Vermittlungen gefunden." %}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
|
{% endwith %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -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,12 +26,30 @@
|
|||||||
|
|
||||||
<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="">
|
<div class="block">
|
||||||
<p>
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
<a class="button is-warning" href="{% url 'password_change' %}">{% translate "Change password" %}</a>
|
<div class="control">
|
||||||
<a class="button is-info" href="{% url 'user-me-export' %}">{% translate "Daten exportieren" %}</a>
|
<a class="button is-warning"
|
||||||
</p>
|
href="{% url 'account_change_password' %}">{% translate "Passwort ändern" %}</a>
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<a class="button is-warning"
|
||||||
|
href="{% url 'account_email' %}">
|
||||||
|
{% translate "E-Mail Adresse ändern" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<a class="button is-warning"
|
||||||
|
href="{% url 'mfa_index' %}">
|
||||||
|
{% translate "2-Faktor Authentifizierung" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<a class="button is-info" href="{% url 'user-me-export' %}">
|
||||||
|
{% translate "Daten exportieren" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -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 %}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{% extends "fellchensammlung/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load custom_tags %}
|
||||||
|
|
||||||
|
{% block title %}<title>{% translate "404 Forbidden" %}</title>{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="title is-1">404 Not Found</h1>
|
||||||
|
<p>
|
||||||
|
{% blocktranslate %}
|
||||||
|
Diese Seite existiert nicht.
|
||||||
|
{% endblocktranslate %}
|
||||||
|
</p>
|
||||||
|
{% endblock %}
|
||||||
@@ -2,14 +2,27 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load custom_tags %}
|
{% load custom_tags %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="content">
|
<div class="block">
|
||||||
|
<div class="message is-warning">
|
||||||
{% if external_site_warning %}
|
{% if external_site_warning %}
|
||||||
|
<h1 class="message-header">
|
||||||
|
{{ external_site_warning.title }}
|
||||||
|
</h1>
|
||||||
|
<div class="message-body content">
|
||||||
{{ external_site_warning.content | render_markdown }}
|
{{ external_site_warning.content | render_markdown }}
|
||||||
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% blocktranslate %}
|
|
||||||
<p>Achtung du verlässt notfellchen.org</p>
|
<h1 class="message-header">
|
||||||
{% endblocktranslate %}
|
{% trans 'Achtung du verlässt notfellchen.org' %}
|
||||||
|
</h1>
|
||||||
|
<div class="message-body">
|
||||||
|
{% trans 'Sichere Abgabebedingungen können von uns, trotz vieler Bemühungen, nicht garantiert werden. Nimm Kontakt zu einer Rattenhilfe oder dem VdRD e.V. auf, die dich beraten können.' %}
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ url }}" class="button is-primary">{% translate "Weiter" %}</a>
|
</div>
|
||||||
|
<div class="block">
|
||||||
|
<a href="{{ url }}" class="button is-primary is-fullwidth">{% translate "Weiter" %}<i class="fa fa-arrow-right fa-fw" aria-hidden="true"></i> </a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@@ -34,6 +34,14 @@
|
|||||||
{% translate 'Das Notfellchen Projekt' %}
|
{% translate 'Das Notfellchen Projekt' %}
|
||||||
</a>
|
</a>
|
||||||
<br/>
|
<br/>
|
||||||
|
<a href="{% url "contact" %}">
|
||||||
|
{% translate 'Kontakt' %}
|
||||||
|
</a>
|
||||||
|
<br/>
|
||||||
|
<a href="{% url "buying" %}">
|
||||||
|
{% translate 'Ratten kaufen' %}
|
||||||
|
</a>
|
||||||
|
<br/>
|
||||||
<a href="{% url "terms-of-service" %}">
|
<a href="{% url "terms-of-service" %}">
|
||||||
{% translate 'Nutzungsbedingungen' %}
|
{% translate 'Nutzungsbedingungen' %}
|
||||||
</a>
|
</a>
|
||||||
@@ -88,6 +96,7 @@
|
|||||||
{% translate 'Tierheime in der Nähe' %}
|
{% translate 'Tierheime in der Nähe' %}
|
||||||
</a>
|
</a>
|
||||||
<br/>
|
<br/>
|
||||||
|
{% if request.user.is_authenticated %}
|
||||||
{% trust_level "MODERATOR" as coordinator_trust_level %}
|
{% trust_level "MODERATOR" as coordinator_trust_level %}
|
||||||
{% if request.user.trust_level >= coordinator_trust_level %}
|
{% if request.user.trust_level >= coordinator_trust_level %}
|
||||||
<a class="nav-link " href="{% url "modtools" %}">
|
<a class="nav-link " href="{% url "modtools" %}">
|
||||||
@@ -100,6 +109,7 @@
|
|||||||
<i class="fa-solid fa-screwdriver-wrench fa-fw"></i>{% translate 'Admin-Bereich' %}
|
<i class="fa-solid fa-screwdriver-wrench fa-fw"></i>{% translate 'Admin-Bereich' %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
@@ -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">
|
|
||||||
<div class="card-header">
|
|
||||||
<div class="card-header-title">
|
|
||||||
{% blocktrans %}
|
|
||||||
Als {{ user }} kommentieren
|
|
||||||
{% endblocktrans %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="action" value="comment">
|
<input type="hidden" name="action" value="comment">
|
||||||
{{ comment_form }}
|
<div class="field">
|
||||||
|
{{ comment_form.text |add_class:"input textarea"|attr:"rows:3"|attr:"placeholder:Neuer Kommentar" }}
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
<input type="submit" class="button is-primary" value="{% trans 'Kommentieren' %}">
|
<input type="submit" class="button is-primary" value="{% trans 'Kommentieren' %}">
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -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">
|
||||||
|
<p>
|
||||||
{% blocktranslate %}
|
{% blocktranslate %}
|
||||||
Lade hier ein Foto hoch - wähle den Titel wie du willst und mach bitte eine Bildbeschreibung,
|
Lade hier ein Foto hoch. Füge bitte eine Bildbeschreibung hinzu,
|
||||||
damit die Fotos auch für blinde und sehbehinderte Personen zugänglich sind.
|
damit die Fotos auch für blinde und sehbehinderte Personen zugänglich sind.
|
||||||
{% endblocktranslate %}
|
{% endblocktranslate %}
|
||||||
<p><a class="button" target="_blank"
|
|
||||||
href="https://www.dbsv.org/bildbeschreibung-4-regeln.html">{% translate 'Anleitung für Bildbeschreibungen' %}</a>
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="block">
|
||||||
<form method="post" enctype="multipart/form-data">
|
<form method="post" enctype="multipart/form-data">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="field">
|
<div class="file has-name is-boxed is-primary is-">
|
||||||
<label class="label" for="image">{{ form.image.label }}</label>
|
<label class="file-label" for="image">
|
||||||
{{ form.image|add_class:"input"|attr:"id:image" }}
|
{{ form.image|add_class:"file-input"|attr:"id:image" }}
|
||||||
|
<span class="file-cta">
|
||||||
|
<span class="file-icon">
|
||||||
|
<i class="fas fa-upload"></i>
|
||||||
|
</span>
|
||||||
|
<span class="file-label">{{ form.image.help_text }}</span>
|
||||||
|
</span>
|
||||||
|
<span class="file-name" id="image-upload-filename">...</span>
|
||||||
|
</label>
|
||||||
</div>
|
</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">
|
<div class="field">
|
||||||
<label class="label" for="alt-text">{{ form.alt_text.label }}</label>
|
<label class="label" for="alt-text">{{ form.alt_text.label }}</label>
|
||||||
{{ form.alt_text|add_class:"textarea"|attr:"id:alt-text"|attr:"rows:3" }}
|
{{ 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 class="is-danger">{{ form.alt_text.errors }}</div>
|
||||||
</div>
|
</div>
|
||||||
<input class="button is-primary" type="submit" value="{% translate "Speichern" %}">
|
<input class="button is-primary" type="submit" value="{% translate "Speichern" %}">
|
||||||
</form>
|
</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 %}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
{% extends "fellchensammlung/base.html" %}
|
{% extends "fellchensammlung/base.html" %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block description %}
|
||||||
|
<meta name="description" content="{% trans 'Inhalt melden' %}">
|
||||||
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 class="title is-1">{% translate "Melden" %}</h1>
|
<h1 class="title is-1">{% translate "Melden" %}</h1>
|
||||||
Wenn dieser Inhalt nicht unseren <a href='{% url "about" %}'>Regeln</a> entspricht, wähle bitte eine der folgenden Regeln aus und hinterlasse einen Kommentar der es detaillierter erklärt, insbesondere wenn der Regelverstoß nicht offensichtlich ist.
|
Wenn dieser Inhalt nicht unseren <a href='{% url "about" %}'>Regeln</a> entspricht, wähle bitte eine der folgenden Regeln aus und hinterlasse einen Kommentar der es detaillierter erklärt, insbesondere wenn der Regelverstoß nicht offensichtlich ist.
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
<h1 class="title is-4">notfellchen.org</h1>
|
<h1 class="title is-4">notfellchen.org</h1>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a role="button" class="navbar-burger" aria-label="{% trans 'Hauptmenü' %}" tabindex="0" aria-expanded="false" data-target="navbarBasicExample">
|
<a role="button" class="navbar-burger" aria-label="{% trans 'Hauptmenü' %}" tabindex="0" aria-expanded="false"
|
||||||
|
data-target="navbarBasicExample">
|
||||||
<span aria-hidden="true"></span>
|
<span aria-hidden="true"></span>
|
||||||
<span aria-hidden="true"></span>
|
<span aria-hidden="true"></span>
|
||||||
<span aria-hidden="true"></span>
|
<span aria-hidden="true"></span>
|
||||||
@@ -30,7 +31,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="navbar-end">
|
<div class="navbar-end">
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
|
<div class="navbar-item">
|
||||||
|
<div class="notification-container">
|
||||||
|
<a class="notification-label" href="{% url 'user-notifications' %}">
|
||||||
|
<i class="fas fa-bell fa-fw"></i>
|
||||||
|
</a>
|
||||||
|
{% if request.user.get_num_unread_notifications > 0 %}
|
||||||
|
<span class="notification-badge">{{ request.user.get_num_unread_notifications }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="navbar-item">
|
<div class="navbar-item">
|
||||||
<a href="{% url 'user-me' %}">
|
<a href="{% url 'user-me' %}">
|
||||||
<i class="fas fa-user fa-fw"></i> {{ user }}
|
<i class="fas fa-user fa-fw"></i> {{ user }}
|
||||||
@@ -39,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 |
@@ -23,7 +23,9 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if introduction %}
|
{% if introduction %}
|
||||||
<h1>{{ introduction.title }}</h1>
|
<h1>{{ introduction.title }}</h1>
|
||||||
|
<div class="content">
|
||||||
{{ introduction.content | render_markdown }}
|
{{ introduction.content | render_markdown }}
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<h2 class="title is-2">{% translate "Aktuelle Vermittlungen" %}</h2>
|
<h2 class="title is-2">{% translate "Aktuelle Vermittlungen" %}</h2>
|
||||||
@@ -44,7 +46,7 @@
|
|||||||
<h2 class="title is-1">{{ how_to.title }}</h2>
|
<h2 class="title is-1">{{ how_to.title }}</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-content">
|
<div class="card-content content">
|
||||||
{{ how_to.content | render_markdown }}
|
{{ how_to.content | render_markdown }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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">
|
||||||
|
{% if expand %}
|
||||||
|
{% include "fellchensammlung/partials/partial-adoption-notice.html" %}
|
||||||
|
{% else %}
|
||||||
{% include "fellchensammlung/partials/partial-adoption-notice-minimal.html" %}
|
{% include "fellchensammlung/partials/partial-adoption-notice-minimal.html" %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
<article class="message is-warning">
|
||||||
|
<div class="message-header">
|
||||||
<p>{% translate "Keine Vermittlungen gefunden." %}</p>
|
<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 %}
|
||||||
|
|||||||
@@ -54,6 +54,11 @@
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.setting-info {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
.content, .header, .footer {
|
.content, .header, .footer {
|
||||||
padding: 20px 15px;
|
padding: 20px 15px;
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{% block content %}{% endblock %}
|
||||||
|
|
||||||
|
---
|
||||||
|
{% include "fellchensammlung/mail/footer.txt" %}
|
||||||
@@ -1,3 +1,12 @@
|
|||||||
|
{% load i18n %}
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
🐀 notfellchen.org | Für Menschen die Ratten aus dem Tierschutz ein liebendes Zuhause geben wollen.
|
🐀 notfellchen.org | Für Menschen die Ratten aus dem Tierschutz ein liebendes Zuhause geben wollen.
|
||||||
|
{% if notification %}
|
||||||
|
<div class="setting-info">
|
||||||
|
{% trans "Du bekommst diese Nachricht basierend auf deinen Benachrichtigungseinstellungen." %}<br>
|
||||||
|
<a href="{{ notification.user_to_notify.get_full_url }}">
|
||||||
|
{% trans "Einstellungen ändern" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
🐀 notfellchen.org | Für Menschen die Ratten aus dem Tierschutz ein liebendes Zuhause geben wollen.
|
||||||
|
|
||||||
|
{% if notification %}
|
||||||
|
{% trans "Du bekommst diese Nachricht basierend auf deinen Benachrichtigungseinstellungen." %}
|
||||||
|
{% trans "Einstellungen ändern" %}: {{ notification.user_to_notify.get_full_url }}
|
||||||
|
{% endif %}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{% extends "fellchensammlung/mail/base.txt" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block content %}{% blocktranslate %}Moin,
|
||||||
|
die Vermittlung {{ notification.adoption_notice }} wurde deaktiviert.
|
||||||
|
{% endblocktranslate %}
|
||||||
|
|
||||||
|
{% translate 'Vermittlung anzeigen' %}: {{ notification.adoption_notice.get_full_url }}{% endblock %}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{% extends "fellchensammlung/mail/base.txt" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block content %}{% blocktranslate %}Moin,
|
||||||
|
|
||||||
|
es wurde eine neue Vermittlung gefunden, die deinen Kriterien entspricht: {{ notification.adoption_notice }}
|
||||||
|
|
||||||
|
|
||||||
|
Vermittlung anzeigen: {{ notification.adoption_notice.get_full_url }}
|
||||||
|
{% endblocktranslate %}{% endblock %}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{% extends "fellchensammlung/mail/base.txt" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block content %}{% blocktranslate %}Moin,
|
||||||
|
|
||||||
|
die Vermittlung {{ notification.adoption_notice }} muss überprüft werden.
|
||||||
|
|
||||||
|
Vermittlung anzeigen: {{ notification.adoption_notice.get_full_url }}
|
||||||
|
{% endblocktranslate %}{% endblock %}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{% extends "fellchensammlung/mail/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load custom_tags %}
|
||||||
|
{% block content %}{% blocktranslate %}Moin,
|
||||||
|
|
||||||
|
folgender Kommentar wurde zur Vermittlung {{ notification.adoption_notice }} hinzugefügt:
|
||||||
|
|
||||||
|
{{ notification.comment.text }}
|
||||||
|
|
||||||
|
Vermittlung anzeigen: {{ notification.adoption_notice.get_full_url }}
|
||||||
|
{% endblocktranslate %}{% endblock %}
|
||||||
@@ -14,6 +14,6 @@
|
|||||||
Details findest du hier
|
Details findest du hier
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<a href="{{ notification.user_related.get_absolute_url }}" class="cta-button">{% translate 'User anzeigen' %}</a>
|
<a href="{{ notification.user_related.get_full_url }}" class="cta-button">{% translate 'User anzeigen' %}</a>
|
||||||
</p>
|
</p>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{% extends "fellchensammlung/mail/base.txt" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block content %}{% blocktranslate with new_user_url=notification.user_related.get_full_url %}Moin,
|
||||||
|
|
||||||
|
es wurde ein neuer Useraccount erstellt.
|
||||||
|
|
||||||
|
User anzeigen: {{ new_user_url }}
|
||||||
|
{% endblocktranslate %}{% endblock %}
|
||||||