Compare commits
22 Commits
2d690d4536
...
e38234b736
Author | SHA1 | Date | |
---|---|---|---|
e38234b736 | |||
ce38002676 | |||
314cdfdd7c | |||
4504a18f60 | |||
df41028e99 | |||
f404cfa0a3 | |||
72dedb6b0c | |||
17468097ec | |||
28331f105a | |||
39893c2185 | |||
ab2b91735e | |||
1b9574cca9 | |||
0d52101f22 | |||
96c0c1218f | |||
864c76bc21 | |||
c3646e6334 | |||
83a219df0c | |||
e6428965c4 | |||
7f31c58abf | |||
1c3d2c7cf5 | |||
90489e01b0 | |||
ed8ccd96f4 |
@ -1,17 +1,6 @@
|
|||||||
---
|
---
|
||||||
|
|
||||||
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:
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
FROM python:3-slim
|
FROM python:3.11-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
|
||||||
|
17
README.md
17
README.md
@ -106,3 +106,20 @@ 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
|
||||||
|
```
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
FROM sphinxdoc/sphinx
|
|
||||||
|
|
||||||
WORKDIR .
|
|
||||||
ADD requirements.txt ./
|
|
||||||
RUN pip3 install -r requirements.txt
|
|
@ -1,3 +1,18 @@
|
|||||||
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.
|
||||||
|
|
||||||
|
@ -35,10 +35,11 @@ dependencies = [
|
|||||||
"markdown",
|
"markdown",
|
||||||
"Pillow",
|
"Pillow",
|
||||||
"django-registration",
|
"django-registration",
|
||||||
"psycopg2-binary",
|
"psycopg2",
|
||||||
"django-crispy-forms",
|
"django-crispy-forms",
|
||||||
"crispy-bootstrap4",
|
"crispy-bootstrap4",
|
||||||
"djangorestframework"
|
"djangorestframework",
|
||||||
|
"celery[redis]"
|
||||||
]
|
]
|
||||||
dynamic = ["version", "readme"]
|
dynamic = ["version", "readme"]
|
||||||
|
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
# 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'),
|
||||||
|
),
|
||||||
|
]
|
@ -134,6 +134,12 @@ 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):
|
||||||
@ -324,6 +330,7 @@ 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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -351,6 +358,11 @@ 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"
|
||||||
@ -516,6 +528,18 @@ 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):
|
||||||
"""
|
"""
|
||||||
@ -523,7 +547,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öffentlichungszeitpunk")
|
publish_start_time = models.DateTimeField(verbose_name="Veröffentlichungszeitpunkt")
|
||||||
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"
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
/***************/
|
||||||
|
/* MAIN COLORS */
|
||||||
|
/***************/
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--primary-light-one: #5daa68;
|
--primary-light-one: #5daa68;
|
||||||
--primary-light-two: #4a9455;
|
--primary-light-two: #4a9455;
|
||||||
@ -18,7 +22,9 @@
|
|||||||
--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%;
|
||||||
@ -30,10 +36,6 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.content-box {
|
|
||||||
margin: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@ -79,6 +81,127 @@ 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);
|
||||||
@ -110,14 +233,7 @@ h1, h2 {
|
|||||||
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;
|
||||||
@ -135,26 +251,6 @@ 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 {
|
||||||
@ -172,25 +268,6 @@ 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;
|
||||||
@ -203,14 +280,6 @@ 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;
|
||||||
@ -326,18 +395,6 @@ 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;
|
||||||
@ -355,10 +412,7 @@ select, .button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.container-cards {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.photos {
|
.photos {
|
||||||
@ -376,14 +430,6 @@ 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);
|
||||||
@ -514,20 +560,7 @@ 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 {
|
||||||
@ -552,6 +585,21 @@ textarea {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.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;
|
||||||
}
|
}
|
||||||
@ -564,15 +612,10 @@ textarea {
|
|||||||
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');
|
||||||
@ -594,4 +637,4 @@ textarea {
|
|||||||
.map-in-content #map {
|
.map-in-content #map {
|
||||||
height: 500px;
|
height: 500px;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
}
|
}
|
||||||
|
19
src/fellchensammlung/tasks.py
Normal file
19
src/fellchensammlung/tasks.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
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)
|
@ -2,6 +2,8 @@
|
|||||||
{% 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" %}
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
{% load custom_tags %}
|
{% load custom_tags %}
|
||||||
|
{% load i18n %}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
{% block title %}<title>Notfellchen</title>{% endblock %}
|
{% block 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' %}">
|
||||||
|
@ -2,23 +2,25 @@
|
|||||||
{% 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 }}
|
||||||
{% if not is_subscribed %}
|
{% if not is_subscribed %}
|
||||||
<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="subscribe">
|
<input type="hidden" name="action" value="subscribe">
|
||||||
<input type="hidden" name="adoption_notice_id" value="{{ adoption_notice.pk }}">
|
<input type="hidden" name="adoption_notice_id" value="{{ adoption_notice.pk }}">
|
||||||
<button class="btn2" type="submit" id="submit"><i class="fa-solid fa-bell"></i></button>
|
<button class="btn2" type="submit" id="submit"><i class="fa-solid fa-bell"></i></button>
|
||||||
</form>
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
<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="unsubscribe">
|
<input type="hidden" name="action" value="unsubscribe">
|
||||||
<input type="hidden" name="adoption_notice_id" value="{{ adoption_notice.pk }}">
|
<input type="hidden" name="adoption_notice_id" value="{{ adoption_notice.pk }}">
|
||||||
<button class="btn2" type="submit" id="submit"><i class="fa-solid fa-bell-slash"></i></button>
|
<button class="btn2" type="submit" id="submit"><i class="fa-solid fa-bell-slash"></i></button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h1>
|
</h1>
|
||||||
{% if has_edit_permission %}
|
{% if has_edit_permission %}
|
||||||
@ -48,7 +50,14 @@
|
|||||||
<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>{{ adoption_notice.link_to_more_information | safe }}</td>
|
<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 %}
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
{% 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 %}
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
{% 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 %}
|
@ -2,6 +2,8 @@
|
|||||||
{% 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>
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
{% 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" %}
|
||||||
|
@ -1,23 +1,24 @@
|
|||||||
{% 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>
|
||||||
{% if missing_texts|length > 0 %}
|
{% if missing_texts|length > 0 %}
|
||||||
<h2>{% trans "Fehlende Texte" %}</h2>
|
<h2>{% trans "Fehlende Texte" %}</h2>
|
||||||
<p>
|
<p>
|
||||||
<table>
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>{% translate "Text Code" %}</th>
|
||||||
|
<th>{% translate "Sprache" %}</th>
|
||||||
|
</tr>
|
||||||
|
{% for missing_text in missing_texts %}
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% translate "Text Code" %}</th>
|
<td>{{ missing_text.0 }}</td>
|
||||||
<th>{% translate "Sprache" %}</th>
|
<td>{{ missing_text.1 }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% for missing_text in missing_texts %}
|
{% endfor %}
|
||||||
<tr>
|
</table>
|
||||||
<td>{{ missing_text.0 }}</td>
|
|
||||||
<td>{{ missing_text.1 }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
</p>
|
</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>{% translate "Texte scheinen vollständig" %}</p>
|
<p>{% translate "Texte scheinen vollständig" %}</p>
|
||||||
@ -55,6 +56,22 @@
|
|||||||
<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">
|
||||||
@ -62,5 +79,13 @@
|
|||||||
<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 %}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{% 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">
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{% 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>
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
{% 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 %}
|
||||||
|
@ -4,6 +4,7 @@ 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()
|
||||||
|
|
||||||
@ -56,6 +57,17 @@ 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)
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
from fellchensammlung.models import AdoptionNotice, Location, RescueOrganization
|
from django.utils import timezone
|
||||||
|
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
|
||||||
@ -7,7 +11,7 @@ def clean_locations(quiet=True):
|
|||||||
num_without_location = adoption_notices_without_location.count()
|
num_without_location = adoption_notices_without_location.count()
|
||||||
if not quiet:
|
if not quiet:
|
||||||
print(f"From {num_of_all} there are {num_without_location} adoption notices without location "
|
print(f"From {num_of_all} there are {num_without_location} adoption notices without location "
|
||||||
f"({num_without_location/num_of_all*100:.2f}%)")
|
f"({num_without_location / num_of_all * 100:.2f}%)")
|
||||||
for adoption_notice in adoption_notices_without_location:
|
for adoption_notice in adoption_notices_without_location:
|
||||||
if not quiet:
|
if not quiet:
|
||||||
print(f"Searching {adoption_notice.location_string} in Nominatim")
|
print(f"Searching {adoption_notice.location_string} in Nominatim")
|
||||||
@ -28,7 +32,7 @@ def clean_locations(quiet=True):
|
|||||||
num_without_location = rescue_orgs_without_location.count()
|
num_without_location = rescue_orgs_without_location.count()
|
||||||
if not quiet:
|
if not quiet:
|
||||||
print(f"From {num_of_all} there are {num_without_location} adoption notices without location "
|
print(f"From {num_of_all} there are {num_without_location} adoption notices without location "
|
||||||
f"({num_without_location/num_of_all*100:.2f}%)")
|
f"({num_without_location / num_of_all * 100:.2f}%)")
|
||||||
for rescue_org in rescue_orgs_without_location:
|
for rescue_org in rescue_orgs_without_location:
|
||||||
if not quiet:
|
if not quiet:
|
||||||
print(f"Searching {rescue_org.location_string} in Nominatim")
|
print(f"Searching {rescue_org.location_string} in Nominatim")
|
||||||
@ -41,4 +45,21 @@ def clean_locations(quiet=True):
|
|||||||
num_without_location_new = rescue_orgs_without_location_new.count()
|
num_without_location_new = rescue_orgs_without_location_new.count()
|
||||||
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()
|
||||||
|
@ -7,7 +7,6 @@ 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
|
||||||
@ -65,7 +64,8 @@ class GeoAPI:
|
|||||||
|
|
||||||
def get_coordinates_from_query(self, location_string):
|
def get_coordinates_from_query(self, location_string):
|
||||||
try:
|
try:
|
||||||
result = self.requests.get(self.api_url, {"q": location_string, "format": "jsonv2"}, headers=self.headers).json()[0]
|
result = \
|
||||||
|
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"]
|
||||||
@ -77,9 +77,9 @@ class GeoAPI:
|
|||||||
def get_geojson_for_query(self, location_string):
|
def get_geojson_for_query(self, location_string):
|
||||||
try:
|
try:
|
||||||
result = self.requests.get(self.api_url,
|
result = self.requests.get(self.api_url,
|
||||||
{"q": location_string,
|
{"q": location_string,
|
||||||
"format": "jsonv2"},
|
"format": "jsonv2"},
|
||||||
headers=self.headers).json()
|
headers=self.headers).json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning(f"Exception {e} when querying Nominatim")
|
logging.warning(f"Exception {e} when querying Nominatim")
|
||||||
return None
|
return None
|
||||||
|
@ -79,5 +79,9 @@ 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"),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
@ -13,14 +13,16 @@ 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, Species
|
User, Location, AdoptionNoticeStatus, Subscriptions, CommentNotification, BaseNotification, RescueOrganization, \
|
||||||
|
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
|
from .tools.admin import clean_locations, get_unchecked_adoption_notices, deactivate_unchecked_adoption_notices
|
||||||
|
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):
|
||||||
@ -46,12 +48,9 @@ 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, "announcements": active_announcements}
|
context = {"adoption_notices": active_adoptions[:5], "adoption_notices_map": active_adoptions,
|
||||||
for text_code in ["how_to", "introduction"]:
|
"announcements": active_announcements}
|
||||||
try:
|
Text.get_texts(["how_to", "introduction"], lang, context)
|
||||||
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)
|
||||||
|
|
||||||
@ -76,7 +75,7 @@ def change_language(request):
|
|||||||
def adoption_notice_detail(request, adoption_notice_id):
|
def adoption_notice_detail(request, adoption_notice_id):
|
||||||
adoption_notice = AdoptionNotice.objects.get(id=adoption_notice_id)
|
adoption_notice = AdoptionNotice.objects.get(id=adoption_notice_id)
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
try:
|
try:
|
||||||
subscription = Subscriptions.objects.get(owner=request.user, adoption_notice=adoption_notice)
|
subscription = Subscriptions.objects.get(owner=request.user, adoption_notice=adoption_notice)
|
||||||
is_subscribed = True
|
is_subscribed = True
|
||||||
except Subscriptions.DoesNotExist:
|
except Subscriptions.DoesNotExist:
|
||||||
@ -104,9 +103,9 @@ def adoption_notice_detail(request, adoption_notice_id):
|
|||||||
# Create a notification but only if the user is not the one that posted the comment
|
# Create a notification but only if the user is not the one that posted the comment
|
||||||
if subscription.owner != request.user:
|
if subscription.owner != request.user:
|
||||||
notification = CommentNotification(user=subscription.owner,
|
notification = CommentNotification(user=subscription.owner,
|
||||||
title=f"{adoption_notice.name} - Neuer Kommentar",
|
title=f"{adoption_notice.name} - Neuer Kommentar",
|
||||||
text=f"{request.user}: {comment_instance.text}",
|
text=f"{request.user}: {comment_instance.text}",
|
||||||
comment=comment_instance)
|
comment=comment_instance)
|
||||||
notification.save()
|
notification.save()
|
||||||
else:
|
else:
|
||||||
comment_form = CommentForm(instance=adoption_notice)
|
comment_form = CommentForm(instance=adoption_notice)
|
||||||
@ -114,13 +113,13 @@ def adoption_notice_detail(request, adoption_notice_id):
|
|||||||
Subscriptions.objects.create(owner=request.user, adoption_notice=adoption_notice)
|
Subscriptions.objects.create(owner=request.user, adoption_notice=adoption_notice)
|
||||||
is_subscribed = True
|
is_subscribed = True
|
||||||
if action == "unsubscribe":
|
if action == "unsubscribe":
|
||||||
subscription.delete()
|
subscription.delete()
|
||||||
is_subscribed = False
|
is_subscribed = False
|
||||||
else:
|
else:
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
else:
|
else:
|
||||||
comment_form = CommentForm(instance=adoption_notice)
|
comment_form = CommentForm(instance=adoption_notice)
|
||||||
context = {"adoption_notice": adoption_notice,"comment_form": comment_form, "user": request.user,
|
context = {"adoption_notice": adoption_notice, "comment_form": comment_form, "user": request.user,
|
||||||
"has_edit_permission": has_edit_permission, "is_subscribed": is_subscribed}
|
"has_edit_permission": has_edit_permission, "is_subscribed": is_subscribed}
|
||||||
return render(request, 'fellchensammlung/details/detail_adoption_notice.html', context=context)
|
return render(request, 'fellchensammlung/details/detail_adoption_notice.html', context=context)
|
||||||
|
|
||||||
@ -170,8 +169,9 @@ def search(request):
|
|||||||
adoption_notices_in_distance = active_adoptions
|
adoption_notices_in_distance = active_adoptions
|
||||||
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, "place_not_found": place_not_found}
|
context = {"adoption_notices": adoption_notices_in_distance, "search_form": search_form,
|
||||||
|
"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,36 +183,39 @@ 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, in_adoption_notice_creation_flow=True)
|
form = AdoptionNoticeFormWithDateWidgetAutoAnimal(request.POST, request.FILES,
|
||||||
|
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]["waiting_for_review"]
|
minor_status = AdoptionNoticeStatus.MINOR_STATUS_CHOICES[AdoptionNoticeStatus.AWAITING_ACTION][
|
||||||
|
"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)
|
||||||
status.save()
|
status.save()
|
||||||
|
|
||||||
# Get the species and number of animals from the form
|
# Get the species and number of animals from the form
|
||||||
species = form.cleaned_data["species"]
|
species = form.cleaned_data["species"]
|
||||||
sex = form.cleaned_data["sex"]
|
sex = form.cleaned_data["sex"]
|
||||||
num_animals = form.cleaned_data["num_animals"]
|
num_animals = form.cleaned_data["num_animals"]
|
||||||
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, date_of_birth=date_of_birth)
|
name=f"{species} {i + 1}", adoption_notice=instance, species=species, sex=sex,
|
||||||
|
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)
|
||||||
@ -423,6 +426,7 @@ 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":
|
||||||
@ -447,7 +451,7 @@ def updatequeue(request):
|
|||||||
|
|
||||||
|
|
||||||
def map(request):
|
def map(request):
|
||||||
adoption_notices = AdoptionNotice.objects.all() #TODO: Filter to active
|
adoption_notices = AdoptionNotice.objects.all() #TODO: Filter to active
|
||||||
context = {"adoption_notices_map": adoption_notices}
|
context = {"adoption_notices_map": adoption_notices}
|
||||||
return render(request, 'fellchensammlung/map.html', context=context)
|
return render(request, 'fellchensammlung/map.html', context=context)
|
||||||
|
|
||||||
@ -456,6 +460,7 @@ 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):
|
||||||
"""
|
"""
|
||||||
@ -465,16 +470,20 @@ 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()
|
||||||
@ -494,10 +503,19 @@ 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)
|
||||||
|
@ -1 +1,8 @@
|
|||||||
__version__ = "0.2.0"
|
__version__ = "0.3.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',)
|
||||||
|
|
||||||
|
25
src/notfellchen/celery.py
Normal file
25
src/notfellchen/celery.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# <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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,7 @@ 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()
|
||||||
@ -79,6 +80,11 @@ 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 """
|
||||||
|
Loading…
Reference in New Issue
Block a user