Compare commits
18 Commits
8b2913a8be
...
964aeb97a7
| Author | SHA1 | Date | |
|---|---|---|---|
| 964aeb97a7 | |||
| 474e9eb0f8 | |||
| 7acc2c6eec | |||
| 5a02837d7f | |||
| 7ff0a9b489 | |||
| 9af4b58a4f | |||
| 7a20890f17 | |||
| f6c9e532f8 | |||
| f1698c4fd3 | |||
| cb82aeffde | |||
| e9c1ef2604 | |||
| d8f0f2b3be | |||
| 65f065f5ce | |||
| 5cba64e500 | |||
| 064784a222 | |||
| b890ef3563 | |||
| 962f2ae86c | |||
| c71a1940dd |
67
docs/_ext/drawio.py
Normal file
67
docs/_ext/drawio.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
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
|
||||||
|
"""
|
||||||
|
|
||||||
|
has_content = False
|
||||||
|
required_arguments = 2 # html and png
|
||||||
|
|
||||||
|
def run(self) -> list[nodes.Node]:
|
||||||
|
html_path = self.arguments[0]
|
||||||
|
png_path = self.arguments[1]
|
||||||
|
|
||||||
|
env = self.state.document.settings.env
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
container = nodes.container()
|
||||||
|
|
||||||
|
# HTML output -> raw HTML node
|
||||||
|
if self.builder.format == "html":
|
||||||
|
# Embed the HTML file contents directly
|
||||||
|
raw_html_node = nodes.raw(
|
||||||
|
"",
|
||||||
|
f'<div class="drawio-diagram">{open(html_path, encoding="utf-8").read()}</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]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def builder(self):
|
||||||
|
# Helper to access the builder from the directive context
|
||||||
|
return self.state.document.settings.env.app.builder
|
||||||
|
|
||||||
|
|
||||||
|
def setup(app: Sphinx) -> ExtensionMetadata:
|
||||||
|
app.add_directive("drawio", DrawioDirective)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"version": "0.1",
|
||||||
|
"parallel_read_safe": True,
|
||||||
|
"parallel_write_safe": True,
|
||||||
|
}
|
||||||
12
docs/conf.py
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
BIN
docs/user/Screenshot-Moderationstools.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 53 KiB |
BIN
docs/user/Screenshot-hilfreiche-Links.png
Normal file
BIN
docs/user/Screenshot-hilfreiche-Links.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
11
docs/user/Tiere-in-Vermittlung-entdecken.drawio.html
Normal file
11
docs/user/Tiere-in-Vermittlung-entdecken.drawio.html
Normal file
File diff suppressed because one or more lines are too long
BIN
docs/user/Tiere-in-Vermittlung-entdecken.drawio.png
Normal file
BIN
docs/user/Tiere-in-Vermittlung-entdecken.drawio.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 120 KiB |
@@ -9,3 +9,4 @@ User Dokumentation
|
|||||||
vermittlungen.rst
|
vermittlungen.rst
|
||||||
moderationskonzept.rst
|
moderationskonzept.rst
|
||||||
benachrichtigungen.rst
|
benachrichtigungen.rst
|
||||||
|
organisationen-pruefen.rst
|
||||||
|
|||||||
46
docs/user/organisationen-pruefen.rst
Normal file
46
docs/user/organisationen-pruefen.rst
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
Tiere in Vermittlung systematisch entdecken & eintragen
|
||||||
|
=======================================================
|
||||||
|
|
||||||
|
Notfellchen hat eine Liste der meisten deutschen Tierheime und anderer Tierschutzorganisationen (550, Stand Oktober 2025).
|
||||||
|
Die meisten dieser Organisationen (412, Stand Oktober 2025) nehmen Tiere auf die bei Notfellchen eingetragen werden können.
|
||||||
|
Es ist daher das Ziel, diese Organisationen alle zwei Wochen auf neue Tiere zu prüfen.
|
||||||
|
|
||||||
|
.. 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` |
|
||||||
|
+------------------------------------------------------+---------------+
|
||||||
@@ -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,108 @@ 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -205,6 +205,8 @@ def main():
|
|||||||
h = {'Authorization': f'Token {api_token}', "content-type": "application/json"}
|
h = {'Authorization': f'Token {api_token}', "content-type": "application/json"}
|
||||||
|
|
||||||
tierheime = overpass_result["features"]
|
tierheime = overpass_result["features"]
|
||||||
|
stats = {"num_updated_orgs": 0,
|
||||||
|
"num_inserted_orgs": 0}
|
||||||
|
|
||||||
for idx, tierheim in enumerate(tqdm(tierheime)):
|
for idx, tierheim in enumerate(tqdm(tierheime)):
|
||||||
# Check if data is low quality
|
# Check if data is low quality
|
||||||
@@ -229,11 +231,13 @@ def main():
|
|||||||
optional_data = ["email", "phone_number", "website", "description", "fediverse_profile", "facebook",
|
optional_data = ["email", "phone_number", "website", "description", "fediverse_profile", "facebook",
|
||||||
"instagram"]
|
"instagram"]
|
||||||
|
|
||||||
# Check if rescue organization exits
|
# Check if rescue organization exists
|
||||||
search_data = {"external_source_identifier": "OSM",
|
search_data = {"external_source_identifier": "OSM",
|
||||||
"external_object_identifier": f"{tierheim["id"]}"}
|
"external_object_identifier": f"{tierheim["id"]}"}
|
||||||
search_result = requests.get(f"{instance}/api/organizations", params=search_data, headers=h)
|
search_result = requests.get(f"{instance}/api/organizations", params=search_data, headers=h)
|
||||||
|
# Rescue organization exits
|
||||||
if search_result.status_code == 200:
|
if search_result.status_code == 200:
|
||||||
|
stats["num_updated_orgs"] += 1
|
||||||
org_id = search_result.json()[0]["id"]
|
org_id = search_result.json()[0]["id"]
|
||||||
logging.debug(f"{th_data.name} already exists as ID {org_id}.")
|
logging.debug(f"{th_data.name} already exists as ID {org_id}.")
|
||||||
org_patch_data = {"id": org_id,
|
org_patch_data = {"id": org_id,
|
||||||
@@ -248,7 +252,9 @@ def main():
|
|||||||
if result.status_code != 200:
|
if result.status_code != 200:
|
||||||
logging.warning(f"Updating {tierheim['properties']['name']} failed:{result.status_code} {result.json()}")
|
logging.warning(f"Updating {tierheim['properties']['name']} failed:{result.status_code} {result.json()}")
|
||||||
continue
|
continue
|
||||||
|
# Rescue organization does not exist
|
||||||
else:
|
else:
|
||||||
|
stats["num_inserted_orgs"] += 1
|
||||||
location = create_location(tierheim, instance, h)
|
location = create_location(tierheim, instance, h)
|
||||||
org_data = {"name": tierheim["properties"]["name"],
|
org_data = {"name": tierheim["properties"]["name"],
|
||||||
"external_object_identifier": f"{tierheim["id"]}",
|
"external_object_identifier": f"{tierheim["id"]}",
|
||||||
@@ -262,6 +268,7 @@ def main():
|
|||||||
|
|
||||||
if result.status_code != 201:
|
if result.status_code != 201:
|
||||||
print(f"{idx} {tierheim["properties"]["name"]}:{result.status_code} {result.json()}")
|
print(f"{idx} {tierheim["properties"]["name"]}:{result.status_code} {result.json()}")
|
||||||
|
print(f"Upload finished. Inserted {stats['num_inserted_orgs']} new orgs and updated {stats['num_updated_orgs']} orgs.")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -35,13 +35,18 @@ class AdoptionNoticeSerializer(serializers.HyperlinkedModelSerializer):
|
|||||||
required=False,
|
required=False,
|
||||||
allow_null=True
|
allow_null=True
|
||||||
)
|
)
|
||||||
|
url = serializers.SerializerMethodField()
|
||||||
|
|
||||||
photos = ImageSerializer(many=True, read_only=True)
|
photos = ImageSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
|
def get_url(self, obj):
|
||||||
|
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", "adoption_notice_status"]
|
"group_only", "location", "location_details", "organization", "photos", "adoption_notice_status",
|
||||||
|
"url"]
|
||||||
|
|
||||||
|
|
||||||
class AdoptionNoticeGeoJSONSerializer(serializers.ModelSerializer):
|
class AdoptionNoticeGeoJSONSerializer(serializers.ModelSerializer):
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -12,7 +12,8 @@ from .tools import misc, geo
|
|||||||
from notfellchen.settings import MEDIA_URL, base_url
|
from notfellchen.settings import MEDIA_URL, base_url
|
||||||
from .tools.geo import LocationProxy, Position
|
from .tools.geo import LocationProxy, Position
|
||||||
from .tools.misc import time_since_as_hr_string
|
from .tools.misc import time_since_as_hr_string
|
||||||
from .tools.model_helpers import NotificationTypeChoices, AdoptionNoticeStatusChoices, AdoptionProcess
|
from .tools.model_helpers import NotificationTypeChoices, AdoptionNoticeStatusChoices, AdoptionProcess, \
|
||||||
|
AdoptionNoticeStatusChoicesDescriptions
|
||||||
from .tools.model_helpers import ndm as NotificationDisplayMapping
|
from .tools.model_helpers import ndm as NotificationDisplayMapping
|
||||||
|
|
||||||
|
|
||||||
@@ -553,9 +554,13 @@ class AdoptionNotice(models.Model):
|
|||||||
def is_awaiting_action(self):
|
def is_awaiting_action(self):
|
||||||
return self.adoption_notice_status in self._values_of(AdoptionNoticeStatusChoices.AwaitingAction.choices)
|
return self.adoption_notice_status in self._values_of(AdoptionNoticeStatusChoices.AwaitingAction.choices)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status_description(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.Disabled.UNCHECKED
|
self.adoption_notice_status = AdoptionNoticeStatusChoices.AwaitingAction.UNCHECKED
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
for subscription in self.get_subscriptions():
|
for subscription in self.get_subscriptions():
|
||||||
|
|||||||
@@ -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 {
|
||||||
@@ -351,3 +352,12 @@ AN Cards
|
|||||||
.embed-main-content {
|
.embed-main-content {
|
||||||
padding: 20px 10px 20px 10px;
|
padding: 20px 10px 20px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FLOATING BUTTON
|
||||||
|
|
||||||
|
.floating {
|
||||||
|
position: fixed;
|
||||||
|
border-radius: 0.3rem;
|
||||||
|
bottom: 4.5rem;
|
||||||
|
right: 1rem;
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,10 @@
|
|||||||
{% translate 'Das Notfellchen Projekt' %}
|
{% translate 'Das Notfellchen Projekt' %}
|
||||||
</a>
|
</a>
|
||||||
<br/>
|
<br/>
|
||||||
|
<a href="{% url "contact" %}">
|
||||||
|
{% translate 'Kontakt' %}
|
||||||
|
</a>
|
||||||
|
<br/>
|
||||||
<a href="{% url "buying" %}">
|
<a href="{% url "buying" %}">
|
||||||
{% translate 'Ratten kaufen' %}
|
{% translate 'Ratten kaufen' %}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -81,6 +81,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="an-organization">
|
||||||
|
{{ form.organization.label }}
|
||||||
|
{% if form.organization.field.required %}<span class="special_class">*</span>{% endif %}
|
||||||
|
</label>
|
||||||
|
<div class="select">
|
||||||
|
{{ form.organization|attr:"id:an-organization" }}
|
||||||
|
</div>
|
||||||
|
<div class="help">
|
||||||
|
{{ form.organization.help_text }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="notification is-info">
|
<div class="notification is-info">
|
||||||
<p>
|
<p>
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="message-body">
|
<div class="message-body">
|
||||||
{% blocktranslate %}
|
{% blocktranslate %}
|
||||||
Versuche es zu einem späteren Zeitpunkt erneut
|
Versuche es zu einem späteren Zeitpunkt erneut.
|
||||||
{% endblocktranslate %}
|
{% endblocktranslate %}
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
<div id="modal-shortcuts" class="modal">
|
||||||
|
<div class="modal-background"></div>
|
||||||
|
<div class="modal-card">
|
||||||
|
<header class="modal-card-head">
|
||||||
|
<p class="modal-card-title">{% trans 'Shortcuts' %}</p>
|
||||||
|
<button class="delete" aria-label="{% trans 'Schließen' %}"></button>
|
||||||
|
</header>
|
||||||
|
<section class="modal-card-body">
|
||||||
|
<div class="content">
|
||||||
|
<ul>
|
||||||
|
<li>{% trans 'Website öffnen' %}: <code>O</code></li>
|
||||||
|
<li>{% trans 'Website schließen' %}: <code>{% trans 'STRG' %} + W</code></li>
|
||||||
|
<li>{% trans 'Organisation als geprüft markieren' %}: <code>{% trans 'STRG' %} + W</code></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<footer class="modal-card-foot">
|
||||||
|
<div class="buttons">
|
||||||
|
<button class="button is-success nf-modal-close">{% translate 'Verstanden' %}</button>
|
||||||
|
<button class="button">{% translate 'Details' %}</button>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<button class="button is-info floating js-modal-trigger" data-target="modal-shortcuts">
|
||||||
|
<i class="fa-solid fa-keyboard fa-fw"></i> {% trans 'Shortcuts' %}
|
||||||
|
</button>
|
||||||
@@ -4,14 +4,10 @@
|
|||||||
{% if adoption_notice.is_closed %}
|
{% if adoption_notice.is_closed %}
|
||||||
<article class="message is-warning">
|
<article class="message is-warning">
|
||||||
<div class="message-header">
|
<div class="message-header">
|
||||||
<p>{% translate 'Vermittlung deaktiviert' %}</p>
|
<p>{% translate 'Vermittlung geschlossen' %}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="message-body content">
|
<div class="message-body content">
|
||||||
{% blocktranslate %}
|
{{ adoption_notice.status_description }}
|
||||||
Diese Vermittlung wurde deaktiviert. Typischerweise passiert das, wenn die Tiere erfolgreich
|
|
||||||
vermittelt wurden.
|
|
||||||
In den Kommentaren findest du ggf. mehr Informationen.
|
|
||||||
{% endblocktranslate %}
|
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
{% elif adoption_notice.is_interested %}
|
{% elif adoption_notice.is_interested %}
|
||||||
@@ -29,14 +25,10 @@
|
|||||||
{% elif adoption_notice.is_awaiting_action %}
|
{% elif adoption_notice.is_awaiting_action %}
|
||||||
<article class="message is-warning">
|
<article class="message is-warning">
|
||||||
<div class="message-header">
|
<div class="message-header">
|
||||||
<p>{% translate 'Warten auf Aktivierung' %}</p>
|
<p>{% translate 'Wartet auf Freigabe von Moderator*innen' %}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="message-body content">
|
<div class="message-body content">
|
||||||
{% blocktranslate %}
|
{{ adoption_notice.status_description }}
|
||||||
Diese Vermittlung muss noch durch Moderator*innen aktiviert werden und taucht daher nicht auf der
|
|
||||||
Startseite auf.
|
|
||||||
Ggf. fehlen noch relevante Informationen.
|
|
||||||
{% endblocktranslate %}
|
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% if org.website %}
|
||||||
|
<a class="panel-block is-active" href="{{ org.website }}" target="_blank">
|
||||||
|
<span class="panel-icon">
|
||||||
|
<i class="fas fa-globe" aria-label="{% translate "Website" %}"></i>
|
||||||
|
</span>
|
||||||
|
{{ org.website }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if org.phone_number %}
|
||||||
|
<a class="panel-block is-active" href="tel:{{ org.phone_number }}">
|
||||||
|
<span class="panel-icon">
|
||||||
|
<i class="fas fa-phone" aria-label="{% translate "Telefonnummer" %}"></i>
|
||||||
|
</span>
|
||||||
|
{{ org.phone_number }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if org.email %}
|
||||||
|
<a class="panel-block is-active" href="mailto:{{ org.email }}">
|
||||||
|
<span class="panel-icon">
|
||||||
|
<i class="fas fa-envelope" aria-label="{% translate "E-Mail" %}"></i>
|
||||||
|
</span>
|
||||||
|
{{ org.email }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if org.fediverse_profile %}
|
||||||
|
<a class="panel-block is-active" href="{{ org.fediverse_profile }}" target="_blank">
|
||||||
|
<span class="panel-icon">
|
||||||
|
<i class="fas fa-hashtag" aria-label="{% translate "Fediverse Profil" %}"></i>
|
||||||
|
</span>
|
||||||
|
{{ org.fediverse_profile }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if org.instagram %}
|
||||||
|
<a class="panel-block is-active" href="{{ org.instagram }}" target="_blank">
|
||||||
|
<span class="panel-icon">
|
||||||
|
<i class="fa-brands fa-instagram" aria-label="{% translate "Instagram Profil" %}"></i>
|
||||||
|
</span>
|
||||||
|
{{ org.instagram }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if org.facebook %}
|
||||||
|
<a class="panel-block is-active" href="{{ org.facebook }}" target="_blank">
|
||||||
|
<span class="panel-icon">
|
||||||
|
<i class="fa-brands fa-facebook" aria-label="{% translate "Facebook Profil" %}"></i>
|
||||||
|
</span>
|
||||||
|
{{ org.facebook }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
@@ -4,58 +4,13 @@
|
|||||||
<div class="panel block">
|
<div class="panel block">
|
||||||
<p class="panel-heading">{% trans "Kontaktdaten" %}</p>
|
<p class="panel-heading">{% trans "Kontaktdaten" %}</p>
|
||||||
{% if org.has_contact_data %}
|
{% if org.has_contact_data %}
|
||||||
{% if org.website %}
|
{% include "fellchensammlung/partials/partial-in-panel-contact-data.html" %}
|
||||||
<a class="panel-block is-active" href="{{ org.website }}" target="_blank">
|
{% elif org.parent_org.has_contact_data %}
|
||||||
<span class="panel-icon">
|
{% with org=org.parent_org %}
|
||||||
<i class="fas fa-globe" aria-label="{% translate "Website" %}"></i>
|
{% include "fellchensammlung/partials/partial-in-panel-contact-data.html" %}
|
||||||
</span>
|
{% endwith %}
|
||||||
{{ org.website }}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if org.phone_number %}
|
|
||||||
<a class="panel-block is-active" href="tel:{{ org.phone_number }}">
|
|
||||||
<span class="panel-icon">
|
|
||||||
<i class="fas fa-phone" aria-label="{% translate "Telefonnummer" %}"></i>
|
|
||||||
</span>
|
|
||||||
{{ org.phone_number }}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if org.email %}
|
|
||||||
<a class="panel-block is-active" href="mailto:{{ org.email }}">
|
|
||||||
<span class="panel-icon">
|
|
||||||
<i class="fas fa-envelope" aria-label="{% translate "E-Mail" %}"></i>
|
|
||||||
</span>
|
|
||||||
{{ org.email }}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if org.fediverse_profile %}
|
|
||||||
<a class="panel-block is-active" href="{{ org.fediverse_profile }}" target="_blank">
|
|
||||||
<span class="panel-icon">
|
|
||||||
<i class="fas fa-hashtag" aria-label="{% translate "Fediverse Profil" %}"></i>
|
|
||||||
</span>
|
|
||||||
{{ org.fediverse_profile }}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if org.instagram %}
|
|
||||||
<a class="panel-block is-active" href="{{ org.instagram }}" target="_blank">
|
|
||||||
<span class="panel-icon">
|
|
||||||
<i class="fa-brands fa-instagram" aria-label="{% translate "Instagram Profil" %}"></i>
|
|
||||||
</span>
|
|
||||||
{{ org.instagram }}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if org.facebook %}
|
|
||||||
<a class="panel-block is-active" href="{{ org.facebook }}" target="_blank">
|
|
||||||
<span class="panel-icon">
|
|
||||||
<i class="fa-brands fa-facebook" aria-label="{% translate "Facebook Profil" %}"></i>
|
|
||||||
</span>
|
|
||||||
{{ org.facebook }}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="panel-block is-active">
|
<div class="panel-block is-active">
|
||||||
<span class="panel-icon">
|
<span class="panel-icon">
|
||||||
<i class="fas fa-x" aria-label="{% translate "E-Mail" %}"></i>
|
<i class="fas fa-x" aria-label="{% translate "E-Mail" %}"></i>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h2 class="card-header-title"><a
|
<h2 class="card-header-title">
|
||||||
href="{{ rescue_organization.get_absolute_url }}"> {{ rescue_organization.name }}</a></h2>
|
<a href="{{ rescue_organization.get_absolute_url }}"> {{ rescue_organization.name }}
|
||||||
|
</a>
|
||||||
|
</h2>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
@@ -16,10 +18,10 @@
|
|||||||
{{ rescue_organization.location_string }}
|
{{ rescue_organization.location_string }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="block content">
|
{% if rescue_organization.description_short %}
|
||||||
{% if rescue_organization.description_short %}
|
<div class="block content">
|
||||||
{{ rescue_organization.description_short | render_markdown }}
|
{{ rescue_organization.description_short | render_markdown }}
|
||||||
{% endif %}
|
</div>
|
||||||
</div>
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -23,9 +23,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
{% if dq %}
|
{% if dq %}
|
||||||
<a class="button is-info" href="{% url 'organization-check' %}">{% translate 'Datenergänzung deaktivieren' %}</a>
|
<a class="button is-info"
|
||||||
|
href="{% url 'organization-check' %}">{% translate 'Datenergänzung deaktivieren' %}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a class="button is-info is-light" href="{% url 'organization-check-dq' %}">{% translate 'Datenergänzung aktivieren' %}</a>
|
<a class="button is-info is-light"
|
||||||
|
href="{% url 'organization-check-dq' %}">{% translate 'Datenergänzung aktivieren' %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -65,4 +67,5 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% include "fellchensammlung/partials/modal-shortcuts.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,4 +1,22 @@
|
|||||||
from fellchensammlung.models import User, AdoptionNotice, AdoptionNoticeStatusChoices
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from fellchensammlung.models import User, AdoptionNotice, AdoptionNoticeStatusChoices, Animal, RescueOrganization
|
||||||
|
|
||||||
|
|
||||||
|
def get_rescue_org_check_stats():
|
||||||
|
timeframe = timezone.now().date() - timedelta(days=14)
|
||||||
|
num_rescue_orgs_to_check = RescueOrganization.objects.filter(exclude_from_check=False).filter(
|
||||||
|
last_checked__lt=timeframe).count()
|
||||||
|
num_rescue_orgs_checked = RescueOrganization.objects.filter(exclude_from_check=False).filter(
|
||||||
|
last_checked__gte=timeframe).count()
|
||||||
|
|
||||||
|
try:
|
||||||
|
percentage_checked = 100 * num_rescue_orgs_checked / (num_rescue_orgs_to_check + num_rescue_orgs_checked)
|
||||||
|
except ZeroDivisionError:
|
||||||
|
percentage_checked = 100
|
||||||
|
return num_rescue_orgs_to_check, num_rescue_orgs_checked, percentage_checked
|
||||||
|
|
||||||
|
|
||||||
def gather_metrics_data():
|
def gather_metrics_data():
|
||||||
@@ -32,6 +50,10 @@ def gather_metrics_data():
|
|||||||
active_animals_per_sex[sex] = number_of_animals
|
active_animals_per_sex[sex] = number_of_animals
|
||||||
active_animals += number_of_animals
|
active_animals += number_of_animals
|
||||||
|
|
||||||
|
num_animal_shelters = RescueOrganization.objects.all().count()
|
||||||
|
|
||||||
|
num_rescue_orgs_to_check, num_rescue_orgs_checked, percentage_checked = get_rescue_org_check_stats()
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'users': num_user,
|
'users': num_user,
|
||||||
'staff': num_staff,
|
'staff': num_staff,
|
||||||
@@ -45,6 +67,12 @@ def gather_metrics_data():
|
|||||||
},
|
},
|
||||||
'adoption_notices_without_location': adoption_notices_without_location,
|
'adoption_notices_without_location': adoption_notices_without_location,
|
||||||
'active_animals': active_animals,
|
'active_animals': active_animals,
|
||||||
'active_animals_per_sex': active_animals_per_sex
|
'active_animals_per_sex': active_animals_per_sex,
|
||||||
|
'rescue_organizations': num_animal_shelters,
|
||||||
|
'rescue_organization_check': {
|
||||||
|
'rescue_orgs_to_check': num_rescue_orgs_to_check,
|
||||||
|
'rescue_orgs_checked': num_rescue_orgs_checked,
|
||||||
|
'percentage_checked': percentage_checked,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return data
|
return data
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ def pluralize(number, letter="e"):
|
|||||||
|
|
||||||
|
|
||||||
def age_as_hr_string(age: datetime.timedelta) -> str:
|
def age_as_hr_string(age: datetime.timedelta) -> str:
|
||||||
days = age.days
|
days = int(age.days)
|
||||||
weeks = age.days / 7
|
weeks = int(age.days / 7)
|
||||||
months = age.days / 30
|
months = int(age.days / 30)
|
||||||
years = age.days / 365
|
years = int(age.days / 365)
|
||||||
if years >= 1:
|
if years >= 1:
|
||||||
months = months - 12 * years
|
months = months - 12 * years
|
||||||
return f'{years:.0f} Jahr{pluralize(years)} und {months:.0f} Monat{pluralize(months)}'
|
return f'{years:.0f} Jahr{pluralize(years)} und {months:.0f} Monat{pluralize(months)}'
|
||||||
@@ -52,9 +52,9 @@ def time_since_as_hr_string(age: datetime.timedelta) -> str:
|
|||||||
elif weeks >= 3:
|
elif weeks >= 3:
|
||||||
text = _("vor %(weeks)d Wochen") % {"weeks": weeks}
|
text = _("vor %(weeks)d Wochen") % {"weeks": weeks}
|
||||||
elif days >= 1:
|
elif days >= 1:
|
||||||
text = ngettext("vor einem Tag","vor %(count)d Tagen", days,) % {"count": days,}
|
text = ngettext("vor einem Tag", "vor %(count)d Tagen", days, ) % {"count": days, }
|
||||||
elif hours >= 1:
|
elif hours >= 1:
|
||||||
text = ngettext("vor einer Stunde", "vor %(count)d Stunden", hours,) % {"count": hours,}
|
text = ngettext("vor einer Stunde", "vor %(count)d Stunden", hours, ) % {"count": hours, }
|
||||||
elif minutes >= 1:
|
elif minutes >= 1:
|
||||||
text = ngettext("vor einer Minute", "vor %(count)d Minuten", minutes, ) % {"count": minutes, }
|
text = ngettext("vor einer Minute", "vor %(count)d Minuten", minutes, ) % {"count": minutes, }
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ class AdoptionNoticeStatusChoices:
|
|||||||
class AwaitingAction(TextChoices):
|
class AwaitingAction(TextChoices):
|
||||||
WAITING_FOR_REVIEW = "awaiting_action_waiting_for_review", _("Waiting for review")
|
WAITING_FOR_REVIEW = "awaiting_action_waiting_for_review", _("Waiting for review")
|
||||||
NEEDS_ADDITIONAL_INFO = "awaiting_action_needs_additional_info", _("Needs additional info")
|
NEEDS_ADDITIONAL_INFO = "awaiting_action_needs_additional_info", _("Needs additional info")
|
||||||
|
UNCHECKED = "awaiting_action_unchecked", _("Unchecked")
|
||||||
|
|
||||||
class Closed(TextChoices):
|
class Closed(TextChoices):
|
||||||
SUCCESSFUL_WITH_NOTFELLCHEN = "closed_successful_with_notfellchen", _("Successful (with Notfellchen)")
|
SUCCESSFUL_WITH_NOTFELLCHEN = "closed_successful_with_notfellchen", _("Successful (with Notfellchen)")
|
||||||
@@ -79,7 +80,6 @@ class AdoptionNoticeStatusChoices:
|
|||||||
|
|
||||||
class Disabled(TextChoices):
|
class Disabled(TextChoices):
|
||||||
AGAINST_RULES = "disabled_against_the_rules", _("Against the rules")
|
AGAINST_RULES = "disabled_against_the_rules", _("Against the rules")
|
||||||
UNCHECKED = "disabled_unchecked", _("Unchecked")
|
|
||||||
OTHER = "disabled_other", _("Other (disabled)")
|
OTHER = "disabled_other", _("Other (disabled)")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -102,14 +102,17 @@ class AdoptionNoticeStatusChoicesDescriptions:
|
|||||||
_ansc.Closed.ANIMAL_DIED: _("Die zu vermittelnden Tiere sind über die Regenbrücke gegangen."),
|
_ansc.Closed.ANIMAL_DIED: _("Die zu vermittelnden Tiere sind über die Regenbrücke gegangen."),
|
||||||
_ansc.Closed.FOR_OTHER_ADOPTION_NOTICE: _("Vermittlung wurde zugunsten einer anderen geschlossen."),
|
_ansc.Closed.FOR_OTHER_ADOPTION_NOTICE: _("Vermittlung wurde zugunsten einer anderen geschlossen."),
|
||||||
_ansc.Closed.NOT_OPEN_ANYMORE: _("Tier(e) stehen nicht mehr zur Vermittlung bereit."),
|
_ansc.Closed.NOT_OPEN_ANYMORE: _("Tier(e) stehen nicht mehr zur Vermittlung bereit."),
|
||||||
|
_ansc.Closed.LINK_TO_MORE_INFO_NOT_REACHABLE: _(
|
||||||
|
"Der Link zu weiteren Informationen ist nicht mehr erreichbar,"
|
||||||
|
"die Vermittlung wurde daher automatisch deaktiviert"),
|
||||||
_ansc.Closed.OTHER: _("Vermittlung geschlossen."),
|
_ansc.Closed.OTHER: _("Vermittlung geschlossen."),
|
||||||
|
|
||||||
_ansc.AwaitingAction.WAITING_FOR_REVIEW: _(
|
_ansc.AwaitingAction.WAITING_FOR_REVIEW: _(
|
||||||
"Deaktiviert bis Moderator*innen die Vermittlung prüfen können."),
|
"Deaktiviert bis Moderator*innen die Vermittlung prüfen können."),
|
||||||
_ansc.AwaitingAction.NEEDS_ADDITIONAL_INFO: _("Deaktiviert bis Informationen nachgetragen werden."),
|
_ansc.AwaitingAction.NEEDS_ADDITIONAL_INFO: _("Deaktiviert bis Informationen nachgetragen werden."),
|
||||||
|
_ansc.AwaitingAction.UNCHECKED: _("Vermittlung deaktiviert bis sie vom Team auf Aktualität geprüft wurde."),
|
||||||
|
|
||||||
_ansc.Disabled.AGAINST_RULES: _("Vermittlung deaktiviert da sie gegen die Regeln verstößt."),
|
_ansc.Disabled.AGAINST_RULES: _("Vermittlung deaktiviert da sie gegen die Regeln verstößt."),
|
||||||
_ansc.Disabled.UNCHECKED: _("Vermittlung deaktiviert bis sie vom Team auf Aktualität geprüft wurde."),
|
|
||||||
_ansc.Disabled.OTHER: _("Vermittlung deaktiviert.")
|
_ansc.Disabled.OTHER: _("Vermittlung deaktiviert.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ urlpatterns = [
|
|||||||
path("ueber-uns/", views.about, name="about"),
|
path("ueber-uns/", views.about, name="about"),
|
||||||
path("impressum/", views.imprint, name="imprint"),
|
path("impressum/", views.imprint, name="imprint"),
|
||||||
path("terms-of-service/", views.terms_of_service, name="terms-of-service"),
|
path("terms-of-service/", views.terms_of_service, name="terms-of-service"),
|
||||||
|
path("kontakt/", views.contact, name="contact"),
|
||||||
path("datenschutz/", views.privacy, name="privacy"),
|
path("datenschutz/", views.privacy, name="privacy"),
|
||||||
path("ratten-kaufen/", views.buying, name="buying"),
|
path("ratten-kaufen/", views.buying, name="buying"),
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ from .models import Language, Announcement
|
|||||||
from .tools import i18n, img
|
from .tools import i18n, img
|
||||||
from .tools.fedi import post_an_to_fedi
|
from .tools.fedi import post_an_to_fedi
|
||||||
from .tools.geo import GeoAPI, zoom_level_for_radius
|
from .tools.geo import GeoAPI, zoom_level_for_radius
|
||||||
from .tools.metrics import gather_metrics_data
|
from .tools.metrics import gather_metrics_data, get_rescue_org_check_stats
|
||||||
from .tools.admin import clean_locations, get_unchecked_adoption_notices, deactivate_unchecked_adoption_notices, \
|
from .tools.admin import clean_locations, get_unchecked_adoption_notices, deactivate_unchecked_adoption_notices, \
|
||||||
deactivate_404_adoption_notices, send_test_email
|
deactivate_404_adoption_notices, send_test_email
|
||||||
from .tasks import post_adoption_notice_save
|
from .tasks import post_adoption_notice_save
|
||||||
@@ -509,6 +509,11 @@ def terms_of_service(request):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def contact(request):
|
||||||
|
text = i18n.get_text_by_language("contact")
|
||||||
|
return render_text(request, text)
|
||||||
|
|
||||||
|
|
||||||
def report_adoption(request, adoption_notice_id):
|
def report_adoption(request, adoption_notice_id):
|
||||||
"""
|
"""
|
||||||
Form to report adoption notices
|
Form to report adoption notices
|
||||||
@@ -674,7 +679,7 @@ def updatequeue(request):
|
|||||||
last_checked_adoption_list = AdoptionNotice.objects.filter(owner=request.user).order_by("last_checked")
|
last_checked_adoption_list = AdoptionNotice.objects.filter(owner=request.user).order_by("last_checked")
|
||||||
adoption_notices_active = [adoption for adoption in last_checked_adoption_list if adoption.is_active]
|
adoption_notices_active = [adoption for adoption in last_checked_adoption_list if adoption.is_active]
|
||||||
adoption_notices_disabled = [adoption for adoption in last_checked_adoption_list if
|
adoption_notices_disabled = [adoption for adoption in last_checked_adoption_list if
|
||||||
adoption.adoption_notice_status == AdoptionNoticeStatusChoices.Disabled.UNCHECKED]
|
adoption.adoption_notice_status == AdoptionNoticeStatusChoices.AwaitingAction.UNCHECKED]
|
||||||
context = {"adoption_notices_disabled": adoption_notices_disabled,
|
context = {"adoption_notices_disabled": adoption_notices_disabled,
|
||||||
"adoption_notices_active": adoption_notices_active}
|
"adoption_notices_active": adoption_notices_active}
|
||||||
return render(request, 'fellchensammlung/updatequeue.html', context=context)
|
return render(request, 'fellchensammlung/updatequeue.html', context=context)
|
||||||
@@ -865,16 +870,7 @@ def rescue_organization_check(request, context=None):
|
|||||||
org.id: RescueOrgInternalComment(instance=org) for org in rescue_orgs_to_comment
|
org.id: RescueOrgInternalComment(instance=org) for org in rescue_orgs_to_comment
|
||||||
}
|
}
|
||||||
|
|
||||||
timeframe = timezone.now().date() - timedelta(days=14)
|
num_rescue_orgs_to_check, num_rescue_orgs_checked, percentage_checked = get_rescue_org_check_stats()
|
||||||
num_rescue_orgs_to_check = RescueOrganization.objects.filter(exclude_from_check=False).filter(
|
|
||||||
last_checked__lt=timeframe).count()
|
|
||||||
num_rescue_orgs_checked = RescueOrganization.objects.filter(exclude_from_check=False).filter(
|
|
||||||
last_checked__gte=timeframe).count()
|
|
||||||
|
|
||||||
try:
|
|
||||||
percentage_checked = 100 * num_rescue_orgs_checked / (num_rescue_orgs_to_check + num_rescue_orgs_checked)
|
|
||||||
except ZeroDivisionError:
|
|
||||||
percentage_checked = 100
|
|
||||||
|
|
||||||
context["rescue_orgs_to_check"] = rescue_orgs_to_check
|
context["rescue_orgs_to_check"] = rescue_orgs_to_check
|
||||||
context["rescue_orgs_last_checked"] = rescue_orgs_last_checked
|
context["rescue_orgs_last_checked"] = rescue_orgs_last_checked
|
||||||
|
|||||||
15
src/tests/test_misc.py
Normal file
15
src/tests/test_misc.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import datetime
|
||||||
|
|
||||||
|
from fellchensammlung.tools.misc import age_as_hr_string
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
|
||||||
|
class AgeTest(TestCase):
|
||||||
|
|
||||||
|
def test_age_as_hr_string(self):
|
||||||
|
self.assertEqual("7 Wochen", age_as_hr_string(datetime.timedelta(days=50)))
|
||||||
|
self.assertEqual("3 Monate", age_as_hr_string(datetime.timedelta(days=100)))
|
||||||
|
self.assertEqual("10 Monate", age_as_hr_string(datetime.timedelta(days=300)))
|
||||||
|
self.assertEqual("1 Jahr und 4 Monate", age_as_hr_string(datetime.timedelta(days=500)))
|
||||||
|
self.assertEqual("1 Jahr und 11 Monate", age_as_hr_string(datetime.timedelta(days=700)))
|
||||||
|
self.assertEqual("2 Jahre und 6 Monate", age_as_hr_string(datetime.timedelta(days=900)))
|
||||||
Reference in New Issue
Block a user