Compare commits

16 Commits

Author SHA1 Message Date
e64cc4bd5f Merge branch 'develop'
# Conflicts:
#	src/locale/en/LC_MESSAGES/django.po
2025-11-09 17:55:11 +01:00
a498971d66 feat: create directory in one go, don't use cache dir and use more standard entrypoint.sh 2025-11-09 17:36:48 +01:00
527ab07b6f trans: fis various 2025-11-08 00:33:35 +01:00
e2e236d650 feat: add button to download all logs 2025-11-08 00:00:54 +01:00
c4100a9ade trans: fix various 2025-11-07 18:17:53 +01:00
9511b46af0 feat: deactivate cache in dev config 2025-11-07 18:13:30 +01:00
5b906a7708 feat: add clean method 2025-11-07 18:12:53 +01:00
d68e836b57 trans: add various english translations 2025-11-07 18:12:37 +01:00
fe77f1da8d trans: add many translations 2025-11-07 17:08:13 +01:00
78b71690c0 feat: Allow toggling ongoing communication status 2025-11-07 14:02:22 +01:00
3b9ee95abc feat: Add log export functionality 2025-11-07 14:01:51 +01:00
b4e50364de fix: dependency 2025-11-03 22:22:22 +01:00
b014b3b227 fix: quotes 2025-11-03 22:22:12 +01:00
99bfe460ee fix: remove debug statement 2025-11-03 18:24:45 +01:00
d4c7caa42d fix: allow empty internal IP 2025-11-03 18:24:20 +01:00
32c8fc88cf feat: add debug command that prints settings 2025-11-03 18:22:11 +01:00
13 changed files with 3175 additions and 835 deletions

View File

@@ -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"]

View File

@@ -9,6 +9,7 @@ host=localhost
secret=CHANGE-ME secret=CHANGE-ME
debug=True debug=True
internal_ips=["127.0.0.1"] internal_ips=["127.0.0.1"]
cache=False
[database] [database]
backend=sqlite3 backend=sqlite3

View File

@@ -40,6 +40,8 @@ dependencies = [
"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"]
@@ -49,7 +51,6 @@ develop = [
"pytest", "pytest",
"coverage", "coverage",
"model_bakery", "model_bakery",
"debug_toolbar",
] ]
docs = [ docs = [
"sphinx", "sphinx",

View File

@@ -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")
@@ -165,11 +172,26 @@ class SocialMediaPostAdmin(admin.ModelAdmin):
@admin.register(Log) @admin.register(Log)
class LogAdmin(admin.ModelAdmin): class LogAdmin(ExtraButtonsMixin, admin.ModelAdmin):
ordering = ["-created_at"] ordering = ["-created_at"]
list_filter = ("action",) list_filter = ("action",)
list_display = ("action", "user", "created_at") 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)

View 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)}")

View File

@@ -419,7 +419,6 @@ class AdoptionNotice(models.Model):
@property @property
def num_per_sex(self): def num_per_sex(self):
print(f"{self.pk} x")
num_per_sex = dict() num_per_sex = dict()
for sex in SexChoices: for sex in SexChoices:
num_per_sex[sex] = len([animal for animal in self.animals if animal.sex == sex]) num_per_sex[sex] = len([animal for animal in self.animals if animal.sex == sex])

View File

@@ -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">

View File

@@ -81,6 +81,9 @@ def is_404(url):
class RequestProfiler: class RequestProfiler:
data = [] data = []
def clear(self):
self.data = []
def add_status(self, status): def add_status(self, status):
self.data.append((time.time(), status)) self.data.append((time.time(), status))

View File

@@ -867,6 +867,9 @@ def rescue_organization_check(request, context=None):
if action == "checked": if action == "checked":
rescue_org.set_checked() rescue_org.set_checked()
Log.objects.create(user=request.user, action="rescue_organization_checked", ) Log.objects.create(user=request.user, action="rescue_organization_checked", )
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)
@@ -929,7 +932,7 @@ def exclude_from_regular_check(request, rescue_organization_id, source="organiza
if to_be_excluded: if to_be_excluded:
Log.objects.create(user=request.user, Log.objects.create(user=request.user,
action="rescue_organization_excluded_from_check", action="rescue_organization_excluded_from_check",
text=f"New status: {form.cleaned_data["regular_check_status"]}") 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

View File

@@ -75,8 +75,18 @@ except configparser.NoSectionError:
DEBUG = config.getboolean('django', 'debug', fallback=False) DEBUG = config.getboolean('django', 'debug', fallback=False)
# Internal IPs # Internal IPs
raw_config_value = config.get("django", "internal_ips", fallback=[]) internal_ip_raw_config_value = config.get("django", "internal_ips", fallback=None)
INTERNAL_IPS = json.loads(raw_config_value) 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")
@@ -89,7 +99,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 + KEYDB """
CELERY_BROKER_URL = config.get("celery", "broker", fallback="redis://localhost:6379/0") CELERY_BROKER_URL = config.get("celery", "broker", fallback="redis://localhost:6379/0")
CELERY_RESULT_BACKEND = config.get("celery", "backend", fallback="redis://localhost:6379/0") CELERY_RESULT_BACKEND = config.get("celery", "backend", fallback="redis://localhost:6379/0")
@@ -235,6 +244,7 @@ INSTALLED_APPS = [
'drf_spectacular_sidecar', # required for Django collectstatic discovery 'drf_spectacular_sidecar', # required for Django collectstatic discovery
'widget_tweaks', 'widget_tweaks',
"debug_toolbar", "debug_toolbar",
'admin_extra_buttons',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@@ -245,7 +255,9 @@ MIDDLEWARE = [
'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',