Compare commits
25 Commits
656a24ef02
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| e64cc4bd5f | |||
| a498971d66 | |||
| 527ab07b6f | |||
| e2e236d650 | |||
| c4100a9ade | |||
| 9511b46af0 | |||
| 5b906a7708 | |||
| d68e836b57 | |||
| fe77f1da8d | |||
| 78b71690c0 | |||
| 3b9ee95abc | |||
| b4e50364de | |||
| b014b3b227 | |||
| 99bfe460ee | |||
| d4c7caa42d | |||
| 32c8fc88cf | |||
| 151ce0d88e | |||
| e07e633651 | |||
| dd3b1fde9d | |||
| 2ffc9b4ba1 | |||
| 22eebd4586 | |||
| e589a048d3 | |||
| 392eb5a7a8 | |||
| 44fa4d4880 | |||
| 9b97cc4cb1 |
@@ -9,15 +9,14 @@ RUN apt install gettext -y
|
|||||||
RUN apt install libpq-dev gcc -y
|
RUN apt install libpq-dev gcc -y
|
||||||
COPY . /app
|
COPY . /app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN mkdir /app/data
|
RUN mkdir /app/data/static -p
|
||||||
RUN mkdir /app/data/static
|
|
||||||
RUN mkdir /app/data/media
|
RUN mkdir /app/data/media
|
||||||
RUN pip install -e . # Without the -e the library static folder will not be copied by collectstatic!
|
RUN pip install --no-cache-dir -e . # Without the -e the library static folder will not be copied by collectstatic!
|
||||||
|
|
||||||
RUN nf collectstatic --noinput
|
RUN nf collectstatic --noinput
|
||||||
RUN nf compilemessages --ignore venv
|
RUN nf compilemessages --ignore venv
|
||||||
|
|
||||||
COPY docker/notfellchen.bash /bin/notfellchen
|
COPY docker/entrypoint.sh /bin/notfellchen
|
||||||
|
|
||||||
EXPOSE 7345
|
EXPOSE 7345
|
||||||
CMD ["notfellchen"]
|
CMD ["notfellchen"]
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ host=localhost
|
|||||||
[django]
|
[django]
|
||||||
secret=CHANGE-ME
|
secret=CHANGE-ME
|
||||||
debug=True
|
debug=True
|
||||||
|
internal_ips=["127.0.0.1"]
|
||||||
|
cache=False
|
||||||
|
|
||||||
[database]
|
[database]
|
||||||
backend=sqlite3
|
backend=sqlite3
|
||||||
|
|||||||
@@ -39,7 +39,9 @@ dependencies = [
|
|||||||
"drf-spectacular[sidecar]",
|
"drf-spectacular[sidecar]",
|
||||||
"django-widget-tweaks",
|
"django-widget-tweaks",
|
||||||
"django-super-deduper",
|
"django-super-deduper",
|
||||||
"django-allauth[mfa]"
|
"django-allauth[mfa]",
|
||||||
|
"django_debug_toolbar",
|
||||||
|
"django-admin-extra-buttons"
|
||||||
]
|
]
|
||||||
|
|
||||||
dynamic = ["version", "readme"]
|
dynamic = ["version", "readme"]
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ from django.utils.html import format_html
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.http import urlencode
|
from django.utils.http import urlencode
|
||||||
|
|
||||||
|
from admin_extra_buttons.api import ExtraButtonsMixin, button, link
|
||||||
|
|
||||||
from .models import Language, Text, ReportComment, ReportAdoptionNotice, Log, Timestamp, SearchSubscription, \
|
from .models import Language, Text, ReportComment, ReportAdoptionNotice, Log, Timestamp, SearchSubscription, \
|
||||||
SpeciesSpecificURL, ImportantLocation, SocialMediaPost
|
SpeciesSpecificURL, ImportantLocation, SocialMediaPost
|
||||||
|
|
||||||
@@ -17,6 +19,21 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from .tools.model_helpers import AdoptionNoticeStatusChoices
|
from .tools.model_helpers import AdoptionNoticeStatusChoices
|
||||||
|
|
||||||
|
|
||||||
|
def export_to_csv_generic(model, queryset):
|
||||||
|
meta = model._meta
|
||||||
|
field_names = [field.name for field in meta.fields]
|
||||||
|
|
||||||
|
response = HttpResponse(content_type='text/csv')
|
||||||
|
response['Content-Disposition'] = 'attachment; filename={}.csv'.format(meta)
|
||||||
|
writer = csv.writer(response)
|
||||||
|
|
||||||
|
writer.writerow(field_names)
|
||||||
|
for obj in queryset:
|
||||||
|
row = writer.writerow([getattr(obj, field) for field in field_names])
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@admin.register(AdoptionNotice)
|
@admin.register(AdoptionNotice)
|
||||||
class AdoptionNoticeAdmin(admin.ModelAdmin):
|
class AdoptionNoticeAdmin(admin.ModelAdmin):
|
||||||
search_fields = ("name__icontains", "description__icontains")
|
search_fields = ("name__icontains", "description__icontains")
|
||||||
@@ -49,17 +66,7 @@ class UserAdmin(admin.ModelAdmin):
|
|||||||
return format_html('<a href="{}">{} Adoption Notices</a>', url, count)
|
return format_html('<a href="{}">{} Adoption Notices</a>', url, count)
|
||||||
|
|
||||||
def export_as_csv(self, request, queryset):
|
def export_as_csv(self, request, queryset):
|
||||||
meta = self.model._meta
|
response = export_to_csv_generic(self.model, queryset)
|
||||||
field_names = [field.name for field in meta.fields]
|
|
||||||
|
|
||||||
response = HttpResponse(content_type='text/csv')
|
|
||||||
response['Content-Disposition'] = 'attachment; filename={}.csv'.format(meta)
|
|
||||||
writer = csv.writer(response)
|
|
||||||
|
|
||||||
writer.writerow(field_names)
|
|
||||||
for obj in queryset:
|
|
||||||
row = writer.writerow([getattr(obj, field) for field in field_names])
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
export_as_csv.short_description = _("Ausgewählte User exportieren")
|
export_as_csv.short_description = _("Ausgewählte User exportieren")
|
||||||
@@ -164,6 +171,28 @@ class SocialMediaPostAdmin(admin.ModelAdmin):
|
|||||||
list_filter = ("platform",)
|
list_filter = ("platform",)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Log)
|
||||||
|
class LogAdmin(ExtraButtonsMixin, admin.ModelAdmin):
|
||||||
|
ordering = ["-created_at"]
|
||||||
|
list_filter = ("action",)
|
||||||
|
list_display = ("action", "user", "created_at")
|
||||||
|
actions = ("export_as_csv",)
|
||||||
|
|
||||||
|
@admin.action(description=_("Ausgewählte Logs exportieren"))
|
||||||
|
def export_as_csv(self, request, queryset):
|
||||||
|
response = export_to_csv_generic(Log, queryset)
|
||||||
|
return response
|
||||||
|
|
||||||
|
@button()
|
||||||
|
def export_all_as_csv(self, request):
|
||||||
|
actual_queryset = Log.objects.all()
|
||||||
|
response = export_to_csv_generic(Log, actual_queryset)
|
||||||
|
return response
|
||||||
|
|
||||||
|
@link(href="https://www.google.com/", visible=lambda btn: True)
|
||||||
|
def invisible(self, button):
|
||||||
|
button.visible = False
|
||||||
|
|
||||||
admin.site.register(Animal)
|
admin.site.register(Animal)
|
||||||
admin.site.register(Species)
|
admin.site.register(Species)
|
||||||
admin.site.register(Rule)
|
admin.site.register(Rule)
|
||||||
@@ -172,5 +201,4 @@ admin.site.register(ModerationAction)
|
|||||||
admin.site.register(Language)
|
admin.site.register(Language)
|
||||||
admin.site.register(Announcement)
|
admin.site.register(Announcement)
|
||||||
admin.site.register(Subscriptions)
|
admin.site.register(Subscriptions)
|
||||||
admin.site.register(Log)
|
|
||||||
admin.site.register(Timestamp)
|
admin.site.register(Timestamp)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
|
from django.forms.widgets import Textarea
|
||||||
|
|
||||||
from .models import AdoptionNotice, Animal, Image, ReportAdoptionNotice, ReportComment, ModerationAction, User, Species, \
|
from .models import AdoptionNotice, Animal, Image, ReportAdoptionNotice, ReportComment, ModerationAction, User, Species, \
|
||||||
Comment, SexChoicesWithAll, DistanceChoices, SpeciesSpecificURL, RescueOrganization
|
Comment, SexChoicesWithAll, DistanceChoices, SpeciesSpecificURL, RescueOrganization
|
||||||
@@ -9,6 +10,8 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from notfellchen.settings import MEDIA_URL
|
from notfellchen.settings import MEDIA_URL
|
||||||
from crispy_forms.layout import Div
|
from crispy_forms.layout import Div
|
||||||
|
|
||||||
|
from .tools.model_helpers import reason_for_signup_label, reason_for_signup_help_text
|
||||||
|
|
||||||
|
|
||||||
def animal_validator(value: str):
|
def animal_validator(value: str):
|
||||||
value = value.lower()
|
value = value.lower()
|
||||||
@@ -138,8 +141,12 @@ class ModerationActionForm(forms.ModelForm):
|
|||||||
|
|
||||||
|
|
||||||
class AddedRegistrationForm(forms.Form):
|
class AddedRegistrationForm(forms.Form):
|
||||||
|
reason_for_signup = forms.CharField(label=reason_for_signup_label,
|
||||||
|
help_text=reason_for_signup_help_text,
|
||||||
|
widget=Textarea)
|
||||||
captcha = forms.CharField(validators=[animal_validator], label=_("Nenne eine bekannte Tierart"), help_text=_(
|
captcha = forms.CharField(validators=[animal_validator], label=_("Nenne eine bekannte Tierart"), help_text=_(
|
||||||
"Bitte nenne hier eine bekannte Tierart (z.B. ein Tier das an der Leine geführt wird). Das Fragen wir dich um sicherzustellen, dass du kein Roboter bist."))
|
"Bitte nenne hier eine bekannte Tierart (z.B. ein Tier das an der Leine geführt wird). Das Fragen wir dich um "
|
||||||
|
"sicherzustellen, dass du kein Roboter bist."))
|
||||||
|
|
||||||
def signup(self, request, user):
|
def signup(self, request, user):
|
||||||
pass
|
pass
|
||||||
|
|||||||
13
src/fellchensammlung/management/commands/print-settings.py
Normal file
13
src/fellchensammlung/management/commands/print-settings.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from django.core.management import BaseCommand
|
||||||
|
|
||||||
|
from notfellchen import settings
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Print the current settings'
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
for key in settings.__dir__():
|
||||||
|
if key.startswith("_") or key == "SECRET_KEY":
|
||||||
|
continue
|
||||||
|
print(f"{key} = {getattr(settings, key)}")
|
||||||
@@ -13,7 +13,8 @@ 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, RegularCheckStatusChoices
|
AdoptionNoticeStatusChoicesDescriptions, RegularCheckStatusChoices, reason_for_signup_label, \
|
||||||
|
reason_for_signup_help_text
|
||||||
from .tools.model_helpers import ndm as NotificationDisplayMapping
|
from .tools.model_helpers import ndm as NotificationDisplayMapping
|
||||||
|
|
||||||
|
|
||||||
@@ -267,10 +268,6 @@ class RescueOrganization(models.Model):
|
|||||||
"""
|
"""
|
||||||
return self.instagram or self.facebook or self.website or self.phone_number or self.email or self.fediverse_profile
|
return self.instagram or self.facebook or self.website or self.phone_number or self.email or self.fediverse_profile
|
||||||
|
|
||||||
def set_exclusion_from_checks(self):
|
|
||||||
self.exclude_from_check = True
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def child_organizations(self):
|
def child_organizations(self):
|
||||||
return RescueOrganization.objects.filter(parent_org=self)
|
return RescueOrganization.objects.filter(parent_org=self)
|
||||||
@@ -311,8 +308,7 @@ class User(AbstractUser):
|
|||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
organization_affiliation = models.ForeignKey(RescueOrganization, on_delete=models.PROTECT, null=True, blank=True,
|
organization_affiliation = models.ForeignKey(RescueOrganization, on_delete=models.PROTECT, null=True, blank=True,
|
||||||
verbose_name=_('Organisation'))
|
verbose_name=_('Organisation'))
|
||||||
reason_for_signup = models.TextField(verbose_name=_("Grund für die Registrierung"), help_text=_(
|
reason_for_signup = models.TextField(verbose_name=reason_for_signup_label, help_text=reason_for_signup_help_text)
|
||||||
"Wir würden gerne wissen warum du dich registriertst, ob du dich z.B. Tiere eines bestimmten Tierheim einstellen willst 'nur mal gucken' willst. Beides ist toll! Wenn du für ein Tierheim/eine Pflegestelle arbeitest kontaktieren wir dich ggf. um dir erweiterte Rechte zu geben."))
|
|
||||||
email_notifications = models.BooleanField(verbose_name=_("Benachrichtigung per E-Mail"), default=True)
|
email_notifications = models.BooleanField(verbose_name=_("Benachrichtigung per E-Mail"), default=True)
|
||||||
REQUIRED_FIELDS = ["reason_for_signup", "email"]
|
REQUIRED_FIELDS = ["reason_for_signup", "email"]
|
||||||
|
|
||||||
@@ -425,7 +421,7 @@ class AdoptionNotice(models.Model):
|
|||||||
def num_per_sex(self):
|
def num_per_sex(self):
|
||||||
num_per_sex = dict()
|
num_per_sex = dict()
|
||||||
for sex in SexChoices:
|
for sex in SexChoices:
|
||||||
num_per_sex[sex] = self.animals.filter(sex=sex).count()
|
num_per_sex[sex] = len([animal for animal in self.animals if animal.sex == sex])
|
||||||
return num_per_sex
|
return num_per_sex
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -515,6 +511,7 @@ class AdoptionNotice(models.Model):
|
|||||||
photos.extend(animal.photos.all())
|
photos.extend(animal.photos.all())
|
||||||
if len(photos) > 0:
|
if len(photos) > 0:
|
||||||
return photos
|
return photos
|
||||||
|
return None
|
||||||
|
|
||||||
def get_photo(self):
|
def get_photo(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -37,6 +37,26 @@
|
|||||||
{% block header %}
|
{% block header %}
|
||||||
{% include "fellchensammlung/header.html" %}
|
{% include "fellchensammlung/header.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
{% if profile %}
|
||||||
|
<div class="profile">
|
||||||
|
<table class="table is-bordered is-fullwidth is-hoverable is-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>Timestamp</td>
|
||||||
|
<td>Status</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for status in profile %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ status.0 }}</td>
|
||||||
|
<td>{{ status.1 }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="main-content">
|
<div class="main-content">
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
@@ -45,5 +65,8 @@
|
|||||||
{% block footer %}
|
{% block footer %}
|
||||||
{% include "fellchensammlung/footer.html" %}
|
{% include "fellchensammlung/footer.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_body %}
|
||||||
|
{% endblock extra_body %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -71,7 +71,22 @@
|
|||||||
value="{{ rescue_org.pk }}">
|
value="{{ rescue_org.pk }}">
|
||||||
<input type="hidden" name="action" value="checked">
|
<input type="hidden" name="action" value="checked">
|
||||||
<button class="" type="submit">{% translate "Organisation geprüft" %}</button>
|
<button class="" type="submit">{% translate "Organisation geprüft" %}</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer-item is-warning">
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden"
|
||||||
|
name="rescue_organization_id"
|
||||||
|
value="{{ rescue_org.pk }}">
|
||||||
|
<input type="hidden" name="action" value="toggle_active_communication">
|
||||||
|
<button class="" type="submit">
|
||||||
|
{% if rescue_org.ongoing_communication %}
|
||||||
|
{% translate "Aktive Kommunikation beendet" %}
|
||||||
|
{% else %}
|
||||||
|
{% translate "Aktive Kommunikation" %}
|
||||||
|
{% endif %}
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer-item is-danger">
|
<div class="card-footer-item is-danger">
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<div class="grid is-col-min-2">
|
<div class="grid is-col-min-2">
|
||||||
{% if adoption_notice.num_per_sex.F > 0 %}
|
{% with num_per_sex=adoption_notice.num_per_sex %}
|
||||||
|
{% if num_per_sex.F > 0 %}
|
||||||
<span class="cell icon-text tag is-medium">
|
<span class="cell icon-text tag is-medium">
|
||||||
<span class="has-text-weight-bold is-size-4">{{ adoption_notice.num_per_sex.F }} </span>
|
<span class="has-text-weight-bold is-size-4">{{ num_per_sex.F }}</span>
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<img class="icon" src="{% static 'fellchensammlung/img/sexes/Female.png' %}"
|
<img class="icon" src="{% static 'fellchensammlung/img/sexes/Female.png' %}"
|
||||||
alt="{% translate 'weibliche Tiere' %}">
|
alt="{% translate 'weibliche Tiere' %}">
|
||||||
@@ -11,9 +12,9 @@
|
|||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if adoption_notice.num_per_sex.I > 0 %}
|
{% if num_per_sex.I > 0 %}
|
||||||
<span class="cell icon-text tag is-medium">
|
<span class="cell icon-text tag is-medium">
|
||||||
<span class="has-text-weight-bold is-size-4">{{ adoption_notice.num_per_sex.I }}</span>
|
<span class="has-text-weight-bold is-size-4">{{ num_per_sex.I }}</span>
|
||||||
|
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<img class="icon"
|
<img class="icon"
|
||||||
@@ -22,18 +23,18 @@
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if adoption_notice.num_per_sex.M > 0 %}
|
{% if num_per_sex.M > 0 %}
|
||||||
<span class="cell icon-text tag is-medium">
|
<span class="cell icon-text tag is-medium">
|
||||||
<span class="has-text-weight-bold is-size-4">{{ adoption_notice.num_per_sex.M }}</span>
|
<span class="has-text-weight-bold is-size-4">{{ num_per_sex.M }}</span>
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<img class="icon" src="{% static 'fellchensammlung/img/sexes/Male.png' %}"
|
<img class="icon" src="{% static 'fellchensammlung/img/sexes/Male.png' %}"
|
||||||
alt="{% translate 'männliche Tiere' %}">
|
alt="{% translate 'männliche Tiere' %}">
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if adoption_notice.num_per_sex.M_N > 0 %}
|
{% if num_per_sex.M_N > 0 %}
|
||||||
<span class="cell icon-text tag is-medium">
|
<span class="cell icon-text tag is-medium">
|
||||||
<span class="has-text-weight-bold is-size-4">{{ adoption_notice.num_per_sex.M_N }}</span>
|
<span class="has-text-weight-bold is-size-4">{{ num_per_sex.M_N }}</span>
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<img class="icon"
|
<img class="icon"
|
||||||
src="{% static 'fellchensammlung/img/sexes/Male Neutered.png' %}"
|
src="{% static 'fellchensammlung/img/sexes/Male Neutered.png' %}"
|
||||||
@@ -41,4 +42,5 @@
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import datetime as datetime
|
import datetime as datetime
|
||||||
import logging
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
from django.utils.translation import ngettext
|
from django.utils.translation import ngettext
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
@@ -75,3 +76,23 @@ def is_404(url):
|
|||||||
return result.status_code == 404
|
return result.status_code == 404
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
logging.warning(f"Request to {url} failed: {e}")
|
logging.warning(f"Request to {url} failed: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
class RequestProfiler:
|
||||||
|
data = []
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.data = []
|
||||||
|
|
||||||
|
def add_status(self, status):
|
||||||
|
self.data.append((time.time(), status))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def as_relative(self):
|
||||||
|
first_ts = self.data[0][0]
|
||||||
|
return [(datum[0] - first_ts, datum[1]) for datum in self.data]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def as_relative_with_ms(self):
|
||||||
|
first_ts = self.data[0][0]
|
||||||
|
return [(f"{(datum[0] - first_ts)*1000:.4}ms", datum[1]) for datum in self.data]
|
||||||
|
|||||||
@@ -134,3 +134,14 @@ class RegularCheckStatusChoices(models.TextChoices):
|
|||||||
EXCLUDED_OTHER_ORG = "excluded_other_org", _("Exkludiert: Andere Organisation wird geprüft")
|
EXCLUDED_OTHER_ORG = "excluded_other_org", _("Exkludiert: Andere Organisation wird geprüft")
|
||||||
EXCLUDED_SCOPE = "excluded_scope", _("Exkludiert: Organisation hat nie Notfellchen-relevanten Vermittlungen")
|
EXCLUDED_SCOPE = "excluded_scope", _("Exkludiert: Organisation hat nie Notfellchen-relevanten Vermittlungen")
|
||||||
EXCLUDED_OTHER = "excluded_other", _("Exkludiert: Anderer Grund")
|
EXCLUDED_OTHER = "excluded_other", _("Exkludiert: Anderer Grund")
|
||||||
|
|
||||||
|
|
||||||
|
##########
|
||||||
|
## USER ##
|
||||||
|
##########
|
||||||
|
|
||||||
|
reason_for_signup_label = _("Grund für die Registrierung")
|
||||||
|
reason_for_signup_help_text = _(
|
||||||
|
"Wir würden gerne wissen warum du dich registrierst, ob du dich z.B. Tiere eines bestimmten Tierheim einstellen "
|
||||||
|
"willst 'nur mal gucken' willst. Beides ist toll! Wenn du für ein Tierheim/eine Pflegestelle arbeitest "
|
||||||
|
"kontaktieren wir dich ggf. um dir erweiterte Rechte zu geben.")
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
from django.contrib.auth.views import redirect_to_login
|
from django.contrib.auth.views import redirect_to_login
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
@@ -37,6 +36,7 @@ from .tools.admin import clean_locations, get_unchecked_adoption_notices, deacti
|
|||||||
from .tasks import post_adoption_notice_save
|
from .tasks import post_adoption_notice_save
|
||||||
from rest_framework.authtoken.models import Token
|
from rest_framework.authtoken.models import Token
|
||||||
|
|
||||||
|
from .tools.misc import RequestProfiler
|
||||||
from .tools.model_helpers import AdoptionNoticeStatusChoices, AdoptionNoticeProcessTemplates, RegularCheckStatusChoices
|
from .tools.model_helpers import AdoptionNoticeStatusChoices, AdoptionNoticeProcessTemplates, RegularCheckStatusChoices
|
||||||
from .tools.search import AdoptionNoticeSearch, RescueOrgSearch
|
from .tools.search import AdoptionNoticeSearch, RescueOrgSearch
|
||||||
|
|
||||||
@@ -242,11 +242,17 @@ def search_important_locations(request, important_location_slug):
|
|||||||
|
|
||||||
|
|
||||||
def search(request, templatename="fellchensammlung/search.html"):
|
def search(request, templatename="fellchensammlung/search.html"):
|
||||||
|
search_profile = RequestProfiler()
|
||||||
|
search_profile.add_status("Start")
|
||||||
|
|
||||||
# A user just visiting the search site did not search, only upon completing the search form a user has really
|
# A user just visiting the search site did not search, only upon completing the search form a user has really
|
||||||
# searched. This will toggle the "subscribe" button
|
# searched. This will toggle the "subscribe" button
|
||||||
searched = False
|
searched = False
|
||||||
|
search_profile.add_status("Init Search")
|
||||||
search = AdoptionNoticeSearch()
|
search = AdoptionNoticeSearch()
|
||||||
|
search_profile.add_status("Search from request starting")
|
||||||
search.adoption_notice_search_from_request(request)
|
search.adoption_notice_search_from_request(request)
|
||||||
|
search_profile.add_status("Search from request finished")
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
searched = True
|
searched = True
|
||||||
if "subscribe_to_search" in request.POST:
|
if "subscribe_to_search" in request.POST:
|
||||||
@@ -266,10 +272,12 @@ def search(request, templatename="fellchensammlung/search.html"):
|
|||||||
subscribed_search = search.get_subscription_or_none(request.user)
|
subscribed_search = search.get_subscription_or_none(request.user)
|
||||||
else:
|
else:
|
||||||
subscribed_search = None
|
subscribed_search = None
|
||||||
|
search_profile.add_status("End of POST")
|
||||||
site_title = _("Suche")
|
site_title = _("Suche")
|
||||||
site_description = _("Ratten in Tierheimen und Rattenhilfen in der Nähe suchen.")
|
site_description = _("Ratten in Tierheimen und Rattenhilfen in der Nähe suchen.")
|
||||||
canonical_url = reverse("search")
|
canonical_url = reverse("search")
|
||||||
|
|
||||||
|
search_profile.add_status("Start of context")
|
||||||
context = {"adoption_notices": search.get_adoption_notices(),
|
context = {"adoption_notices": search.get_adoption_notices(),
|
||||||
"search_form": search.search_form,
|
"search_form": search.search_form,
|
||||||
"place_not_found": search.place_not_found,
|
"place_not_found": search.place_not_found,
|
||||||
@@ -286,6 +294,10 @@ def search(request, templatename="fellchensammlung/search.html"):
|
|||||||
"site_title": site_title,
|
"site_title": site_title,
|
||||||
"site_description": site_description,
|
"site_description": site_description,
|
||||||
"canonical_url": canonical_url}
|
"canonical_url": canonical_url}
|
||||||
|
search_profile.add_status("End of context")
|
||||||
|
if request.user.is_superuser:
|
||||||
|
context["profile"] = search_profile.as_relative_with_ms
|
||||||
|
search_profile.add_status("Finished - returing render")
|
||||||
return render(request, templatename, context=context)
|
return render(request, templatename, context=context)
|
||||||
|
|
||||||
|
|
||||||
@@ -578,12 +590,20 @@ def report_detail_success(request, report_id):
|
|||||||
|
|
||||||
|
|
||||||
def user_detail(request, user, token=None):
|
def user_detail(request, user, token=None):
|
||||||
|
user_detail_profile = RequestProfiler()
|
||||||
|
user_detail_profile.add_status("Start")
|
||||||
|
adoption_notices = AdoptionNotice.objects.filter(owner=user)
|
||||||
|
user_detail_profile.add_status("Finished fetching adoption notices")
|
||||||
context = {"user": user,
|
context = {"user": user,
|
||||||
"adoption_notices": AdoptionNotice.objects.filter(owner=user),
|
"adoption_notices": adoption_notices,
|
||||||
"notifications": Notification.objects.filter(user_to_notify=user, read=False),
|
"notifications": Notification.objects.filter(user_to_notify=user, read=False),
|
||||||
"search_subscriptions": SearchSubscription.objects.filter(owner=user), }
|
"search_subscriptions": SearchSubscription.objects.filter(owner=user), }
|
||||||
|
user_detail_profile.add_status("End of context")
|
||||||
if token is not None:
|
if token is not None:
|
||||||
context["token"] = token
|
context["token"] = token
|
||||||
|
user_detail_profile.add_status("Finished - returning to renderer")
|
||||||
|
if request.user.is_superuser:
|
||||||
|
context["profile"] = user_detail_profile.as_relative_with_ms
|
||||||
return render(request, 'fellchensammlung/details/detail-user.html', context=context)
|
return render(request, 'fellchensammlung/details/detail-user.html', context=context)
|
||||||
|
|
||||||
|
|
||||||
@@ -846,8 +866,10 @@ def rescue_organization_check(request, context=None):
|
|||||||
action = request.POST.get("action")
|
action = request.POST.get("action")
|
||||||
if action == "checked":
|
if action == "checked":
|
||||||
rescue_org.set_checked()
|
rescue_org.set_checked()
|
||||||
elif action == "exclude":
|
Log.objects.create(user=request.user, action="rescue_organization_checked", )
|
||||||
rescue_org.set_exclusion_from_checks()
|
elif action == "toggle_active_communication":
|
||||||
|
rescue_org.ongoing_communication = not rescue_org.ongoing_communication
|
||||||
|
rescue_org.save()
|
||||||
elif action == "set_species_url":
|
elif action == "set_species_url":
|
||||||
species_url_form = SpeciesURLForm(request.POST)
|
species_url_form = SpeciesURLForm(request.POST)
|
||||||
|
|
||||||
@@ -858,6 +880,7 @@ def rescue_organization_check(request, context=None):
|
|||||||
elif action == "update_internal_comment":
|
elif action == "update_internal_comment":
|
||||||
comment_form = RescueOrgInternalComment(request.POST, instance=rescue_org)
|
comment_form = RescueOrgInternalComment(request.POST, instance=rescue_org)
|
||||||
if comment_form.is_valid():
|
if comment_form.is_valid():
|
||||||
|
Log.objects.create(user=request.user, action="rescue_organization_added_internal_comment", )
|
||||||
comment_form.save()
|
comment_form.save()
|
||||||
|
|
||||||
rescue_orgs_to_check = RescueOrganization.objects.filter(exclude_from_check=False,
|
rescue_orgs_to_check = RescueOrganization.objects.filter(exclude_from_check=False,
|
||||||
@@ -906,6 +929,10 @@ def exclude_from_regular_check(request, rescue_organization_id, source="organiza
|
|||||||
to_be_excluded = form.cleaned_data["regular_check_status"] != RegularCheckStatusChoices.REGULAR_CHECK
|
to_be_excluded = form.cleaned_data["regular_check_status"] != RegularCheckStatusChoices.REGULAR_CHECK
|
||||||
rescue_org.exclude_from_check = to_be_excluded
|
rescue_org.exclude_from_check = to_be_excluded
|
||||||
rescue_org.save()
|
rescue_org.save()
|
||||||
|
if to_be_excluded:
|
||||||
|
Log.objects.create(user=request.user,
|
||||||
|
action="rescue_organization_excluded_from_check",
|
||||||
|
text=f"New status: {form.cleaned_data['regular_check_status']}")
|
||||||
|
|
||||||
return redirect(reverse(source))
|
return redirect(reverse(source))
|
||||||
else:
|
else:
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,7 @@ https://docs.djangoproject.com/en/3.2/topics/settings/
|
|||||||
For the full list of settings and their values, see
|
For the full list of settings and their values, see
|
||||||
https://docs.djangoproject.com/en/3.2/ref/settings/
|
https://docs.djangoproject.com/en/3.2/ref/settings/
|
||||||
"""
|
"""
|
||||||
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import os
|
import os
|
||||||
import configparser
|
import configparser
|
||||||
@@ -74,6 +74,20 @@ except configparser.NoSectionError:
|
|||||||
raise BaseException("No config found or no Django Secret is configured!")
|
raise BaseException("No config found or no Django Secret is configured!")
|
||||||
DEBUG = config.getboolean('django', 'debug', fallback=False)
|
DEBUG = config.getboolean('django', 'debug', fallback=False)
|
||||||
|
|
||||||
|
# Internal IPs
|
||||||
|
internal_ip_raw_config_value = config.get("django", "internal_ips", fallback=None)
|
||||||
|
if internal_ip_raw_config_value:
|
||||||
|
INTERNAL_IPS = json.loads(internal_ip_raw_config_value)
|
||||||
|
|
||||||
|
# Cache
|
||||||
|
if config.getboolean('django', 'cache', fallback=False):
|
||||||
|
CACHES = {
|
||||||
|
"default": {
|
||||||
|
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
|
||||||
|
"LOCATION": "uniques-snowflake",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
""" DATABASE """
|
""" DATABASE """
|
||||||
DB_BACKEND = config.get("database", "backend", fallback="sqlite3")
|
DB_BACKEND = config.get("database", "backend", fallback="sqlite3")
|
||||||
DB_NAME = config.get("database", "name", fallback="notfellchen.sqlite3")
|
DB_NAME = config.get("database", "name", fallback="notfellchen.sqlite3")
|
||||||
@@ -141,7 +155,7 @@ AUTHENTICATION_BACKENDS = [
|
|||||||
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
|
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
|
||||||
ACCOUNT_EMAIL_VERIFICATION_BY_CODE_ENABLED = True
|
ACCOUNT_EMAIL_VERIFICATION_BY_CODE_ENABLED = True
|
||||||
|
|
||||||
ACCOUNT_SIGNUP_FIELDS = ['username*', "email*", "password1*", "password2*"]
|
ACCOUNT_SIGNUP_FIELDS = ['username*', "email*", "password1*"]
|
||||||
|
|
||||||
ACCOUNT_SIGNUP_FORM_CLASS = 'fellchensammlung.forms.AddedRegistrationForm'
|
ACCOUNT_SIGNUP_FORM_CLASS = 'fellchensammlung.forms.AddedRegistrationForm'
|
||||||
|
|
||||||
@@ -229,16 +243,21 @@ INSTALLED_APPS = [
|
|||||||
'drf_spectacular',
|
'drf_spectacular',
|
||||||
'drf_spectacular_sidecar', # required for Django collectstatic discovery
|
'drf_spectacular_sidecar', # required for Django collectstatic discovery
|
||||||
'widget_tweaks',
|
'widget_tweaks',
|
||||||
|
"debug_toolbar",
|
||||||
|
'admin_extra_buttons',
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
"debug_toolbar.middleware.DebugToolbarMiddleware",
|
||||||
# Static file serving & caching
|
# Static file serving & caching
|
||||||
'whitenoise.middleware.WhiteNoiseMiddleware',
|
'whitenoise.middleware.WhiteNoiseMiddleware',
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
# Needs to be after SessionMiddleware and before CommonMiddleware
|
# Needs to be after SessionMiddleware and before CommonMiddleware
|
||||||
'django.middleware.locale.LocaleMiddleware',
|
'django.middleware.locale.LocaleMiddleware',
|
||||||
|
"django.middleware.cache.UpdateCacheMiddleware",
|
||||||
'django.middleware.common.CommonMiddleware',
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
"django.middleware.cache.FetchFromCacheMiddleware",
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
|||||||
@@ -18,13 +18,14 @@ from django.conf.urls.i18n import i18n_patterns
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
|
|
||||||
|
from debug_toolbar.toolbar import debug_toolbar_urls
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path('accounts/', include('allauth.urls')),
|
path('accounts/', include('allauth.urls')),
|
||||||
]
|
] + debug_toolbar_urls()
|
||||||
|
|
||||||
urlpatterns += i18n_patterns(
|
urlpatterns += i18n_patterns(
|
||||||
path("", include("fellchensammlung.urls")),
|
path("", include("fellchensammlung.urls")),
|
||||||
|
|||||||
Reference in New Issue
Block a user