Compare commits

..

No commits in common. "e38234b7366a5f4afaa19de5a6d28c0fae1102db" and "2d690d4536e5bd6ba869bc647826719b10b88294" have entirely different histories.

29 changed files with 190 additions and 470 deletions

View File

@ -1,6 +1,17 @@
--- ---
steps: steps:
create_docker_image:
image: woodpeckerci/plugin-docker-buildx
settings:
repo: moanos/sphinx-rtd
dockerfile: docs/Dockerfile
tag: latest
username:
from_secret: docker_username
password:
from_secret: docker_password
build: build:
image: moanos/sphinx-rtd image: moanos/sphinx-rtd
commands: commands:

View File

@ -1,12 +1,10 @@
FROM python:3.11-slim FROM python:3-slim
# Use 3.11 to avoid django.core.exceptions.ImproperlyConfigured: Error loading psycopg2 or psycopg module
MAINTAINER Julian-Samuel Gebühr MAINTAINER Julian-Samuel Gebühr
ENV DOCKER_BUILD=true ENV DOCKER_BUILD=true
RUN apt update RUN apt update
RUN apt install gettext -y RUN apt install gettext -y
RUN apt install libpq-dev gcc -y
COPY . /app COPY . /app
WORKDIR /app WORKDIR /app
RUN mkdir /app/data RUN mkdir /app/data

View File

@ -106,20 +106,3 @@ Use a program like `gtranslator` or `poedit` to start translations
| Edit adoption notice | User that created, Moderator, Admin | | Edit adoption notice | User that created, Moderator, Admin |
| Edit animal | User that created, Moderator, Admin | | Edit animal | User that created, Moderator, Admin |
| Add animal/photo to adoption notice | User that created, Moderator, Admin | | Add animal/photo to adoption notice | User that created, Moderator, Admin |
# Celery and KeyDB
Start KeyDB docker container
```zsh
docker run -d --name keydb -p 6379:6379 eqalpha/keydb
```
Start worker
```zsh
celery -A notfellchen.celery worker
```
Start beat
```zsh
celery -A notfellchen.celery beat
```

5
docs/Dockerfile Normal file
View File

@ -0,0 +1,5 @@
FROM sphinxdoc/sphinx
WORKDIR .
ADD requirements.txt ./
RUN pip3 install -r requirements.txt

View File

@ -1,18 +1,3 @@
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.
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.
Nach 3 Wochen ohne Prüfung werden Anzeigen automatisch von der Seite entfernt und nur dann wieder freigeschaltet, wenn eine manuelle Prüfung erfolgt.
Darüber hinaus werden einmal täglich die verlinkten Seiten automatisiert geprüft. Wenn eine Vermittlungs-Seite bei einem Tierheim oder einer Pflegestelle entfernt wurde, wird die Anzeige ebenfalls deaktiviert.
Vermittlungen können von allen Menschen, auch ohne Account gemeldet werden. Grund dafür kann sein, dass Informationen veraltet sind oder Verdacht von Tierwohlgefärdung. Gemeldete Vermittlungen werden vom Moderationsteam geprüft und ggf. entfernt.
Die Kommentarfunktion von Vermittlungen ermöglicht es angemeldeten Nutzer*innen zusätzliche Informationen hinzuzufügen oder Fragen zu stellen.
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.

View File

@ -35,11 +35,10 @@ dependencies = [
"markdown", "markdown",
"Pillow", "Pillow",
"django-registration", "django-registration",
"psycopg2", "psycopg2-binary",
"django-crispy-forms", "django-crispy-forms",
"crispy-bootstrap4", "crispy-bootstrap4",
"djangorestframework", "djangorestframework"
"celery[redis]"
] ]
dynamic = ["version", "readme"] dynamic = ["version", "readme"]

View File

@ -1,23 +0,0 @@
# Generated by Django 5.1.1 on 2024-10-10 14:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fellchensammlung', '0007_alter_adoptionnotice_last_checked'),
]
operations = [
migrations.AlterField(
model_name='adoptionnoticestatus',
name='minor_status',
field=models.CharField(choices=[('searching', 'searching'), ('interested', 'interested'), ('waiting_for_review', 'waiting_for_review'), ('needs_additional_info', 'needs_additional_info'), ('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'), ('against_the_rules', 'against_the_rules'), ('missing_information', 'missing_information'), ('technical_error', 'technical_error'), ('unchecked', 'unchecked')], max_length=200),
),
migrations.AlterField(
model_name='announcement',
name='publish_start_time',
field=models.DateTimeField(verbose_name='Veröffentlichungszeitpunkt'),
),
]

View File

@ -134,12 +134,6 @@ class Location(models.Model):
) )
return location return location
@staticmethod
def add_location_to_object(instance):
"""Search the location given in the location string and add it to the object"""
location = Location.get_location_from_string(instance.location_string)
instance.location = location
instance.save()
class RescueOrganization(models.Model): class RescueOrganization(models.Model):
def __str__(self): def __str__(self):
@ -330,7 +324,6 @@ class AdoptionNoticeStatus(models.Model):
"against_the_rules": "against_the_rules", "against_the_rules": "against_the_rules",
"missing_information": "missing_information", "missing_information": "missing_information",
"technical_error": "technical_error", "technical_error": "technical_error",
"unchecked": "unchecked",
"other": "other" "other": "other"
} }
} }
@ -358,11 +351,6 @@ class AdoptionNoticeStatus(models.Model):
self.minor_status = self.MINOR_STATUS_CHOICES[self.CLOSED]["other"] self.minor_status = self.MINOR_STATUS_CHOICES[self.CLOSED]["other"]
self.save() self.save()
def deactivate_unchecked(self):
self.major_status = self.MAJOR_STATUS_CHOICES[self.DISABLED]
self.minor_status = self.MINOR_STATUS_CHOICES[self.DISABLED]["unchecked"]
self.save()
class Animal(models.Model): class Animal(models.Model):
MALE_NEUTERED = "M_N" MALE_NEUTERED = "M_N"
@ -528,18 +516,6 @@ class Text(models.Model):
def __str__(self): def __str__(self):
return f"{self.title} ({self.language})" return f"{self.title} ({self.language})"
@staticmethod
def get_texts(text_codes, language, expandable_dict=None):
if expandable_dict is None:
expandable_dict = {}
for text_code in text_codes:
try:
expandable_dict[text_code] = Text.objects.get(text_code=text_code, language=language, )
except Text.DoesNotExist:
expandable_dict[text_code] = None
return expandable_dict
class Announcement(Text): class Announcement(Text):
""" """
@ -547,7 +523,7 @@ class Announcement(Text):
""" """
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)
publish_start_time = models.DateTimeField(verbose_name="Veröffentlichungszeitpunkt") publish_start_time = models.DateTimeField(verbose_name="Veröffentlichungszeitpunk")
publish_end_time = models.DateTimeField(verbose_name="Veröffentlichungsende") publish_end_time = models.DateTimeField(verbose_name="Veröffentlichungsende")
IMPORTANT = "important" IMPORTANT = "important"
WARNING = "warning" WARNING = "warning"

View File

@ -1,7 +1,3 @@
/***************/
/* MAIN COLORS */
/***************/
:root { :root {
--primary-light-one: #5daa68; --primary-light-one: #5daa68;
--primary-light-two: #4a9455; --primary-light-two: #4a9455;
@ -22,9 +18,7 @@
--text-three: var(--primary-light-one); --text-three: var(--primary-light-one);
--shadow-three: var(--primary-dark-one); --shadow-three: var(--primary-dark-one);
} }
/**************************/
/* TAG SETTINGS (GENERAL) */
/**************************/
html, body { html, body {
margin: 0; margin: 0;
height: 100%; height: 100%;
@ -36,6 +30,10 @@ body {
} }
.content-box {
margin: 20px;
}
table { table {
width: 100%; width: 100%;
} }
@ -81,127 +79,6 @@ h1, h2 {
box-sizing: border-box; box-sizing: border-box;
} }
textarea {
border-radius: 10px;
width: 100%;
margin: 5px;
}
/**************/
/* CONTAINERS */
/**************/
.container-cards {
display: flex;
flex-wrap: wrap;
}
.card {
flex: 1 25%;
margin: 10px;
border-radius: 8px;
padding: 5px;
background: var(--background-three);
color: var(--text-two);
}
.container-edit-buttons {
display: flex;
flex-wrap: wrap;
.btn {
margin: 5px;
}
}
/*******************************/
/* PARTIAL SPECIFIC CONTAINERS */
/*******************************/
.detail-animal-header {
border-radius: 5px;
display: flex;
align-items: center;
justify-content: space-between;
}
@media screen and (max-width: 800px) {
.detail-animal-header {
display: block;
}
}
.profile-card {
display: flex;
border-radius: 0px 0px 8px 8px;
background-color: var(--highlight-two);
color: var(--highlight-one-text);
.btn2 {
height: 40px;
}
.button_darken:hover {
background-color: var(--highlight-one);
color: var(--highlight-one-text);
}
button {
background: inherit;
color: inherit;
}
}
.container-comment-form {
width: 80%;
color: var(--text-one);
b {
text-shadow: 2px 2px var(--shadow-one);
}
}
/***********/
/* BUTTONS */
/***********/
select, .button {
width: 100%;
border: none;
border-radius: 4px;
opacity: 1;
background-color: var(--secondary-light-one);
}
.btn {
background-color: var(--primary-light-one);
color: var(--secondary-light-one);
padding: 16px;
border-radius: 8px;
border: none;
font-weight: bold;
}
.btn2 {
background-color: var(--secondary-light-one);
color: var(--primary-dark-one);
padding: 8px;
border-radius: 4px;
border: none;
margin: 5px;
}
/*********************/
/* UNIQUE COMPONENTS */
/*********************/
.content-box {
margin: 20px;
}
.header { .header {
overflow: hidden; overflow: hidden;
background-color: var(--background-two); background-color: var(--background-two);
@ -233,7 +110,14 @@ select, .button {
color: white; color: white;
} }
select, .button {
width: 100%;
border: none;
border-radius: 4px;
opacity: 1;
background-color: var(--secondary-light-one);
}
.header-right select.option { .header-right select.option {
color: #000; color: #000;
@ -251,6 +135,26 @@ select, .button {
height: 67px; height: 67px;
} }
.profile-card {
display: flex;
border-radius: 0px 0px 8px 8px;
background-color: var(--highlight-two);
color: var(--highlight-one-text);
.btn2 {
height: 40px;
}
.button_darken:hover {
background-color: var(--highlight-one);
color: var(--highlight-one-text);
}
button {
background: inherit;
color: inherit;
}
}
@media screen and (max-width: 500px) { @media screen and (max-width: 500px) {
.header a { .header a {
@ -268,6 +172,25 @@ select, .button {
height: 40px; height: 40px;
} }
.btn {
background-color: var(--primary-light-one);
color: var(--secondary-light-one);
padding: 16px;
border-radius: 8px;
border: none;
font-weight: bold;
}
.btn2 {
background-color: var(--secondary-light-one);
color: var(--primary-dark-one);
padding: 8px;
border-radius: 4px;
border: none;
margin: 5px;
}
.form-button, .link-button a:link, .link-button a:visited { .form-button, .link-button a:link, .link-button a:visited {
background-color: #4ba3cd; background-color: #4ba3cd;
color: white; color: white;
@ -280,6 +203,14 @@ select, .button {
border: none; border: none;
} }
.container-edit-buttons {
display: flex;
flex-wrap: wrap;
.btn {
margin: 5px;
}
}
.form-button:hover, .link-button a:hover, .link-button a:active { .form-button:hover, .link-button a:hover, .link-button a:active {
background-color: #4090b6; background-color: #4090b6;
@ -395,6 +326,18 @@ select, .button {
} }
} }
.detail-animal-header {
border-radius: 5px;
display: flex;
align-items: center;
justify-content: space-between;
}
@media screen and (max-width: 800px) {
.detail-animal-header {
display: block;
}
}
.tag { .tag {
border: black 1px solid; border: black 1px solid;
@ -412,7 +355,10 @@ select, .button {
} }
.container-cards {
display: flex;
flex-wrap: wrap;
}
.photos { .photos {
@ -430,6 +376,14 @@ select, .button {
border-radius: 10%; border-radius: 10%;
} }
.card {
flex: 1 25%;
margin: 10px;
border-radius: 8px;
padding: 5px;
background: var(--background-three);
color: var(--text-two);
}
.card h1 { .card h1 {
color: var(--text-three); color: var(--text-three);
@ -560,7 +514,20 @@ select, .button {
color: var(--text-two); color: var(--text-two);
} }
.container-comment-form {
width: 80%;
color: var(--text-one);
b {
text-shadow: 2px 2px var(--shadow-one);
}
}
textarea {
border-radius: 10px;
width: 100%;
margin: 5px;
}
.form-comments { .form-comments {
.btn { .btn {
@ -585,21 +552,6 @@ select, .button {
} }
} }
.form-search {
select, input {
background-color: var(--primary-light-one);
color: var(--text-one);
border-radius: 3px;
border: none;
}
}
/************************/
/* GENERAL HIGHLIGHTING */
/************************/
.important { .important {
border: #e01137 4px solid; border: #e01137 4px solid;
} }
@ -612,10 +564,15 @@ select, .button {
border: rgba(17, 58, 224, 0.51) 4px solid; border: rgba(17, 58, 224, 0.51) 4px solid;
} }
.form-search {
select, input {
background-color: var(--primary-light-one);
color: var(--text-one);
border-radius: 3px;
border: none;
}
/*******/ }
/* MAP */
/*******/
.marker { .marker {
background-image: url('../img/logo_transparent.png'); background-image: url('../img/logo_transparent.png');

View File

@ -1,19 +0,0 @@
from notfellchen.celery import app as celery_app
from .tools.admin import clean_locations, deactivate_unchecked_adoption_notices
from .models import Location, AdoptionNotice
@celery_app.task(name="admin.clean_locations")
def task_clean_locations():
clean_locations()
@celery_app.task(name="admin.deactivate_unchecked")
def task_deactivate_unchecked():
deactivate_unchecked_adoption_notices()
@celery_app.task(name="commit.add_location")
def add_adoption_notice_location(pk):
instance = AdoptionNotice.objects.get(pk=pk)
Location.add_location_to_object(instance)

View File

@ -2,8 +2,6 @@
{% load i18n %} {% load i18n %}
{% load custom_tags %} {% load custom_tags %}
{% block title %}<title>{% translate "Über uns und Regeln" %}</title> %}{% endblock %}
{% block content %} {% block content %}
<h1>{% translate "Regeln" %}</h1> <h1>{% translate "Regeln" %}</h1>
{% include "fellchensammlung/lists/list-rules.html" %} {% include "fellchensammlung/lists/list-rules.html" %}

View File

@ -1,12 +1,10 @@
{% load custom_tags %} {% load custom_tags %}
{% load i18n %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
{% block title %}{% endblock %} {% block title %}<title>Notfellchen</title>{% endblock %}
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="{% translate "Farbratten aus dem Tierschutz finden und adoptieren" %}">
<!-- Add additional CSS in static file --> <!-- Add additional CSS in static file -->
{% load static %} {% load static %}
<link rel="stylesheet" href="{% static 'fellchensammlung/css/styles.css' %}"> <link rel="stylesheet" href="{% static 'fellchensammlung/css/styles.css' %}">

View File

@ -2,8 +2,6 @@
{% load custom_tags %} {% load custom_tags %}
{% load i18n %} {% load i18n %}
{% block title %}<title>{{adoption_notice.name }}</title>{% endblock %}
{% block content %} {% block content %}
<div class="detail-adoption-notice-header"> <div class="detail-adoption-notice-header">
<h1 class="detail-adoption-notice-header">{{ adoption_notice.name }} <h1 class="detail-adoption-notice-header">{{ adoption_notice.name }}
@ -50,14 +48,7 @@
<td>{{ adoption_notice.searching_since }}</td> <td>{{ adoption_notice.searching_since }}</td>
<td>{{ adoption_notice.last_checked | date:'d. F Y' }}</td> <td>{{ adoption_notice.last_checked | date:'d. F Y' }}</td>
{% if adoption_notice.further_information %} {% if adoption_notice.further_information %}
<td> <td>{{ adoption_notice.link_to_more_information | safe }}</td>
<form method="get" action="{% url 'external-site' %}">
<input type="hidden" name="url" value="{{ adoption_notice.further_information }}">
<button class="btn" type="submit" id="submit">
{{ adoption_notice.further_information | domain }} <i class="fa-solid fa-arrow-up-right-from-square"></i>
</button>
</form>
</td>
{% else %} {% else %}
<td>-</td> <td>-</td>
{% endif %} {% endif %}

View File

@ -2,8 +2,6 @@
{% load custom_tags %} {% load custom_tags %}
{% load i18n %} {% load i18n %}
{% block title %}<title>{{ animal.name }}</title> %}
{% block content %} {% block content %}
{% include "fellchensammlung/details/detail-animal-partial.html" %} {% include "fellchensammlung/details/detail-animal-partial.html" %}
{% endblock %} {% endblock %}

View File

@ -1,10 +0,0 @@
{% extends "fellchensammlung/base_generic.html" %}
{% load i18n %}
{% block content %}
<div class="card">
{% blocktranslate %}
<p>Achtung du verlässt notfellchen.org</p>
{% endblocktranslate %}
<a href="{{ url }}" class="btn" >{% translate "Weiter" %}</a>
</div>
{% endblock content %}

View File

@ -2,8 +2,6 @@
{% load i18n %} {% load i18n %}
{% load crispy_forms_tags %} {% load crispy_forms_tags %}
{% block title %}<title>{% translate "Vermittlung hinzufügen" %}</title> %}{% endblock %}
{% block content %} {% block content %}
<h1>{% translate "Vermitteln" %}</h1> <h1>{% translate "Vermitteln" %}</h1>
<p> <p>

View File

@ -2,8 +2,6 @@
{% load i18n %} {% load i18n %}
{% load custom_tags %} {% load custom_tags %}
{% block title %}<title>{% translate "Notfellchen - Farbratten aus dem Tierschutz adoptieren" %}</title>{% endblock %}
{% block content %} {% block content %}
{% for announcement in announcements %} {% for announcement in announcements %}
{% include "fellchensammlung/partials/partial-announcement.html" %} {% include "fellchensammlung/partials/partial-announcement.html" %}

View File

@ -1,6 +1,5 @@
{% extends "fellchensammlung/base_generic.html" %} {% extends "fellchensammlung/base_generic.html" %}
{% load i18n %} {% load i18n %}
{% block title %}<title>{% translate "Instanz-Check" %}</title> {% endblock %}
{% block content %} {% block content %}
<div class="card"> <div class="card">
<h1>{% translate "Instanz-Check" %}</h1> <h1>{% translate "Instanz-Check" %}</h1>
@ -56,22 +55,6 @@
<p>{{ number_not_geocoded_rescue_orgs }}/{{ number_of_rescue_orgs }}</p> <p>{{ number_not_geocoded_rescue_orgs }}/{{ number_of_rescue_orgs }}</p>
{% endif %} {% endif %}
<h2>{% translate "Nicht-geprüfte Vermittlungen" %}</h2>
{% if number_unchecked_ans > 0 %}
<details>
<summary>{{ number_unchecked_ans }}</summary>
<ul>
{% for unchecked_an in unchecked_ans %}
<li>
<a href="{{ unchecked_an.get_absolute_url }}">{{ unchecked_an.name }}</a>
</li>
{% endfor %}
</ul>
</details>
{% else %}
<p>{{ number_unchecked_ans }}</p>
{% endif %}
<form class="notification-card-mark-read" method="post"> <form class="notification-card-mark-read" method="post">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="action" value="clean_locations"> <input type="hidden" name="action" value="clean_locations">
@ -79,13 +62,5 @@
<i class="fa-solid fa-broom"></i> {% translate "Erneut lokalisieren" %} <i class="fa-solid fa-broom"></i> {% translate "Erneut lokalisieren" %}
</button> </button>
</form> </form>
<form class="notification-card-mark-read" method="post">
{% csrf_token %}
<input type="hidden" name="action" value="deactivate_unchecked_adoption_notices">
<button class="btn" type="submit" id="submit">
<i class="fa-solid fa-broom"></i> {% translate "Deaktivire ungeprüfte Vermittlungen" %}
</button>
</form>
</div> </div>
{% endblock content %} {% endblock content %}

View File

@ -1,6 +1,5 @@
{% extends "fellchensammlung/base_generic.html" %} {% extends "fellchensammlung/base_generic.html" %}
{% load i18n %} {% load i18n %}
{% block title %}<title>{% translate "Karte" %}</title> %}
{% block content %} {% block content %}
<div class="card"> <div class="card">

View File

@ -1,6 +1,5 @@
{% extends "fellchensammlung/base_generic.html" %} {% extends "fellchensammlung/base_generic.html" %}
{% load i18n %} {% load i18n %}
{% block title %}<title>{% translate "Modqueue" %}</title> %}{% endblock %}
{% block content %} {% block content %}
<h1>{% translate "Modqueue" %}</h1> <h1>{% translate "Modqueue" %}</h1>

View File

@ -1,8 +1,5 @@
{% extends "fellchensammlung/base_generic.html" %} {% extends "fellchensammlung/base_generic.html" %}
{% load i18n %} {% load i18n %}
{% block title %}<title>{% translate "Suche" %}</title> %}
{% block content %} {% block content %}
<form class="form-search card" method="post"> <form class="form-search card" method="post">
{% csrf_token %} {% csrf_token %}

View File

@ -4,7 +4,6 @@ from django import template
from django.template.defaultfilters import stringfilter from django.template.defaultfilters import stringfilter
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from notfellchen import settings from notfellchen import settings
from urllib.parse import urlparse
register = template.Library() register = template.Library()
@ -57,17 +56,6 @@ def pointdecimal(value):
except ValueError: except ValueError:
return value return value
@register.filter
@stringfilter
def domain(url):
try:
domain = urlparse(url).netloc
if domain.startswith("www."):
return domain[4:]
return domain
except ValueError:
return url
@register.simple_tag @register.simple_tag
def settings_value(name): def settings_value(name):
return getattr(settings, name) return getattr(settings, name)

View File

@ -1,8 +1,4 @@
from django.utils import timezone from fellchensammlung.models import AdoptionNotice, Location, RescueOrganization
from datetime import timedelta
from fellchensammlung.models import AdoptionNotice, Location, RescueOrganization, AdoptionNoticeStatus
def clean_locations(quiet=True): def clean_locations(quiet=True):
# ADOPTION NOTICES # ADOPTION NOTICES
@ -46,20 +42,3 @@ def clean_locations(quiet=True):
num_new = num_without_location - num_without_location_new num_new = num_without_location - num_without_location_new
if not quiet: if not quiet:
print(f"Added {num_new} new locations") print(f"Added {num_new} new locations")
def get_unchecked_adoption_notices(weeks=3):
now = timezone.now()
three_weeks_ago = now - timedelta(weeks=weeks)
# Query for active adoption notices that were checked in the last three weeks
unchecked_adoptions = AdoptionNotice.objects.filter(
last_checked__gte=three_weeks_ago
)
active_unchecked_adoptions = [adoption for adoption in unchecked_adoptions if adoption.is_active]
return active_unchecked_adoptions
def deactivate_unchecked_adoption_notices():
for adoption_notice in get_unchecked_adoption_notices(weeks=3):
AdoptionNoticeStatus.objects.get(adoption_notice=adoption_notice).deactivate_unchecked()

View File

@ -7,6 +7,7 @@ from notfellchen import __version__ as nf_version
from notfellchen import settings from notfellchen import settings
def calculate_distance_between_coordinates(position1, position2): def calculate_distance_between_coordinates(position1, position2):
""" """
Calculate the distance between two points identified by coordinates Calculate the distance between two points identified by coordinates
@ -64,8 +65,7 @@ class GeoAPI:
def get_coordinates_from_query(self, location_string): def get_coordinates_from_query(self, location_string):
try: try:
result = \ result = self.requests.get(self.api_url, {"q": location_string, "format": "jsonv2"}, headers=self.headers).json()[0]
self.requests.get(self.api_url, {"q": location_string, "format": "jsonv2"}, headers=self.headers).json()[0]
except IndexError: except IndexError:
return None return None
return result["lat"], result["lon"] return result["lat"], result["lon"]

View File

@ -79,9 +79,5 @@ urlpatterns = [
######### #########
path('api/', include('fellchensammlung.api.urls')), path('api/', include('fellchensammlung.api.urls')),
###################
## External Site ##
###################
path('external-site/', views.external_site_warning, name="external-site"),
] ]

View File

@ -13,16 +13,14 @@ from notfellchen import settings
from fellchensammlung import logger from fellchensammlung import logger
from .models import AdoptionNotice, Text, Animal, Rule, Image, Report, ModerationAction, \ from .models import AdoptionNotice, Text, Animal, Rule, Image, Report, ModerationAction, \
User, Location, AdoptionNoticeStatus, Subscriptions, CommentNotification, BaseNotification, RescueOrganization, \ User, Location, AdoptionNoticeStatus, Subscriptions, CommentNotification, BaseNotification, RescueOrganization, Species
Species
from .forms import AdoptionNoticeForm, AdoptionNoticeFormWithDateWidget, ImageForm, ReportAdoptionNoticeForm, \ from .forms import AdoptionNoticeForm, AdoptionNoticeFormWithDateWidget, ImageForm, ReportAdoptionNoticeForm, \
CommentForm, ReportCommentForm, AnimalForm, \ CommentForm, ReportCommentForm, AnimalForm, \
AdoptionNoticeSearchForm, AnimalFormWithDateWidget, AdoptionNoticeFormWithDateWidgetAutoAnimal AdoptionNoticeSearchForm, AnimalFormWithDateWidget, AdoptionNoticeFormWithDateWidgetAutoAnimal
from .models import Language, Announcement from .models import Language, Announcement
from .tools.geo import GeoAPI from .tools.geo import GeoAPI
from .tools.metrics import gather_metrics_data from .tools.metrics import gather_metrics_data
from .tools.admin import clean_locations, get_unchecked_adoption_notices, deactivate_unchecked_adoption_notices from .tools.admin import clean_locations
from .tasks import add_adoption_notice_location
def user_is_trust_level_or_above(user, trust_level=User.MODERATOR): def user_is_trust_level_or_above(user, trust_level=User.MODERATOR):
@ -48,9 +46,12 @@ def index(request):
lang = Language.objects.get(languagecode=language_code) lang = Language.objects.get(languagecode=language_code)
active_announcements = Announcement.get_active_announcements(lang) active_announcements = Announcement.get_active_announcements(lang)
context = {"adoption_notices": active_adoptions[:5], "adoption_notices_map": active_adoptions, context = {"adoption_notices": active_adoptions[:5], "adoption_notices_map": active_adoptions, "announcements": active_announcements}
"announcements": active_announcements} for text_code in ["how_to", "introduction"]:
Text.get_texts(["how_to", "introduction"], lang, context) try:
context[text_code] = Text.objects.get(text_code=text_code, language=lang, )
except Text.DoesNotExist:
context[text_code] = None
return render(request, 'fellchensammlung/index.html', context=context) return render(request, 'fellchensammlung/index.html', context=context)
@ -170,8 +171,7 @@ def search(request):
else: else:
adoption_notices_in_distance = [a for a in active_adoptions if a.in_distance(search_position, max_distance)] adoption_notices_in_distance = [a for a in active_adoptions if a.in_distance(search_position, max_distance)]
context = {"adoption_notices": adoption_notices_in_distance, "search_form": search_form, context = {"adoption_notices": adoption_notices_in_distance, "search_form": search_form, "place_not_found": place_not_found}
"place_not_found": place_not_found}
else: else:
latest_adoption_list = AdoptionNotice.objects.order_by("-created_at") latest_adoption_list = AdoptionNotice.objects.order_by("-created_at")
active_adoptions = [adoption for adoption in latest_adoption_list if adoption.is_active] active_adoptions = [adoption for adoption in latest_adoption_list if adoption.is_active]
@ -183,25 +183,23 @@ def search(request):
@login_required @login_required
def add_adoption_notice(request): def add_adoption_notice(request):
if request.method == 'POST': if request.method == 'POST':
form = AdoptionNoticeFormWithDateWidgetAutoAnimal(request.POST, request.FILES, form = AdoptionNoticeFormWithDateWidgetAutoAnimal(request.POST, request.FILES, in_adoption_notice_creation_flow=True)
in_adoption_notice_creation_flow=True)
if form.is_valid(): if form.is_valid():
instance = form.save(commit=False) instance = form.save(commit=False)
instance.owner = request.user instance.owner = request.user
"""Search the location given in the location string and add it to the adoption notice"""
location = Location.get_location_from_string(instance.location_string)
instance.location = location
instance.save() instance.save()
"""Spin up a task that adds the location"""
add_adoption_notice_location.delay_on_commit(instance.pk)
# Set correct status # Set correct status
if request.user.trust_level >= User.TRUST_LEVEL[User.COORDINATOR]: if request.user.trust_level >= User.TRUST_LEVEL[User.COORDINATOR]:
major_status = AdoptionNoticeStatus.ACTIVE major_status = AdoptionNoticeStatus.ACTIVE
minor_status = AdoptionNoticeStatus.MINOR_STATUS_CHOICES[AdoptionNoticeStatus.ACTIVE]["searching"] minor_status = AdoptionNoticeStatus.MINOR_STATUS_CHOICES[AdoptionNoticeStatus.ACTIVE]["searching"]
else: else:
major_status=AdoptionNoticeStatus.AWAITING_ACTION major_status=AdoptionNoticeStatus.AWAITING_ACTION
minor_status = AdoptionNoticeStatus.MINOR_STATUS_CHOICES[AdoptionNoticeStatus.AWAITING_ACTION][ minor_status=AdoptionNoticeStatus.MINOR_STATUS_CHOICES[AdoptionNoticeStatus.AWAITING_ACTION]["waiting_for_review"]
"waiting_for_review"]
status = AdoptionNoticeStatus.objects.create(major_status=major_status, status = AdoptionNoticeStatus.objects.create(major_status=major_status,
minor_status=minor_status, minor_status=minor_status,
adoption_notice=instance) adoption_notice=instance)
@ -214,8 +212,7 @@ def add_adoption_notice(request):
date_of_birth = form.cleaned_data["date_of_birth"] date_of_birth = form.cleaned_data["date_of_birth"]
for i in range(0, num_animals): for i in range(0, num_animals):
Animal.objects.create(owner=request.user, Animal.objects.create(owner=request.user,
name=f"{species} {i + 1}", adoption_notice=instance, species=species, sex=sex, name=f"{species} {i+1}", adoption_notice=instance, species=species, sex=sex, date_of_birth=date_of_birth)
date_of_birth=date_of_birth)
return redirect(reverse("adoption-notice-detail", args=[instance.pk])) return redirect(reverse("adoption-notice-detail", args=[instance.pk]))
else: else:
form = AdoptionNoticeFormWithDateWidgetAutoAnimal(in_adoption_notice_creation_flow=True) form = AdoptionNoticeFormWithDateWidgetAutoAnimal(in_adoption_notice_creation_flow=True)
@ -426,7 +423,6 @@ def modqueue(request):
context = {"reports": open_reports} context = {"reports": open_reports}
return render(request, 'fellchensammlung/modqueue.html', context=context) return render(request, 'fellchensammlung/modqueue.html', context=context)
@login_required @login_required
def updatequeue(request): def updatequeue(request):
if request.method == "POST": if request.method == "POST":
@ -460,7 +456,6 @@ def metrics(request):
data = gather_metrics_data() data = gather_metrics_data()
return JsonResponse(data) return JsonResponse(data)
@login_required @login_required
def instance_health_check(request): def instance_health_check(request):
""" """
@ -470,20 +465,16 @@ def instance_health_check(request):
action = request.POST.get("action") action = request.POST.get("action")
if action == "clean_locations": if action == "clean_locations":
clean_locations(quiet=False) clean_locations(quiet=False)
elif action == "deactivate_unchecked_adoption_notices":
deactivate_unchecked_adoption_notices()
number_of_adoption_notices = AdoptionNotice.objects.all().count() number_of_adoption_notices = AdoptionNotice.objects.all().count()
none_geocoded_adoption_notices = AdoptionNotice.objects.filter(location__isnull=True) none_geocoded_adoption_notices = AdoptionNotice.objects.filter(location__isnull=True)
number_not_geocoded_adoption_notices = len(none_geocoded_adoption_notices) number_not_geocoded_adoption_notices = len(none_geocoded_adoption_notices)
number_of_rescue_orgs = RescueOrganization.objects.all().count() number_of_rescue_orgs = RescueOrganization.objects.all().count()
none_geocoded_rescue_orgs = RescueOrganization.objects.filter(location__isnull=True) none_geocoded_rescue_orgs = RescueOrganization.objects.filter(location__isnull=True)
number_not_geocoded_rescue_orgs = len(none_geocoded_rescue_orgs) number_not_geocoded_rescue_orgs = len(none_geocoded_rescue_orgs)
unchecked_ans = get_unchecked_adoption_notices()
number_unchecked_ans = len(unchecked_ans)
# CHECK FOR MISSING TEXTS # CHECK FOR MISSING TEXTS
languages = Language.objects.all() languages = Language.objects.all()
texts = Text.objects.all() texts = Text.objects.all()
@ -503,19 +494,10 @@ def instance_health_check(request):
"number_of_rescue_orgs": number_of_rescue_orgs, "number_of_rescue_orgs": number_of_rescue_orgs,
"number_not_geocoded_rescue_orgs": number_not_geocoded_rescue_orgs, "number_not_geocoded_rescue_orgs": number_not_geocoded_rescue_orgs,
"none_geocoded_rescue_orgs": none_geocoded_rescue_orgs, "none_geocoded_rescue_orgs": none_geocoded_rescue_orgs,
"missing_texts": missing_texts, "missing_texts": missing_texts
"number_unchecked_ans": number_unchecked_ans,
"unchecked_ans": unchecked_ans
} }
return render(request, 'fellchensammlung/instance-health-check.html', context=context) return render(request, 'fellchensammlung/instance-health-check.html', context=context)
def external_site_warning(request):
url = request.GET.get("url")
context = {"url": url}
language_code = translation.get_language()
lang = Language.objects.get(languagecode=language_code)
Text.get_texts(["external_site_warning", "good_adoption_practices"], language=lang)
return render(request, 'fellchensammlung/external_site_warning.html', context=context)

View File

@ -1,8 +1 @@
__version__ = "0.3.0" __version__ = "0.2.0"
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app
__all__ = ('celery_app',)

View File

@ -1,25 +0,0 @@
# <your_project>/celery.py
import os
from celery import Celery
from celery.schedules import crontab
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'notfellchen.settings')
app = Celery('notfellchen')
# Load task modules from all registered Django app configs.
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()
app.conf.beat_schedule = {
'daily-cleanup': {
'task': 'admin.clean_locations',
'schedule': crontab(hour=2),
},
'daily-deactivation': {
'task': 'admin.deactivate_unchecked',
'schedule': 30,
}
}

View File

@ -14,7 +14,6 @@ from pathlib import Path
import os import os
import configparser import configparser
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from celery import Celery
"""CONFIG PARSER """ """CONFIG PARSER """
config = configparser.RawConfigParser() config = configparser.RawConfigParser()
@ -80,11 +79,6 @@ DB_HOST = config.get("database", "host", fallback='')
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale')] LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale')]
""" CELERY + KEYDB """
CELERY_BROKER_URL = config.get("celery", "broker", fallback="redis://localhost:6379/0")
CELERY_RESULT_BACKEND = config.get("celery", "backend", fallback="redis://localhost:6379/0")
""" GEOCODING """ """ GEOCODING """
GEOCODING_API_URL = config.get("geocoding", "api_url", fallback="https://nominatim.hyteck.de/search") GEOCODING_API_URL = config.get("geocoding", "api_url", fallback="https://nominatim.hyteck.de/search")
""" Tile Server """ """ Tile Server """