diff --git a/app/bird/admin.py b/app/bird/admin.py index 23c25d1..ba718d0 100644 --- a/app/bird/admin.py +++ b/app/bird/admin.py @@ -21,7 +21,14 @@ class FallenBirdAdmin(admin.ModelAdmin): @admin.register(Bird) class BirdAdmin(admin.ModelAdmin): - list_display = ["name"] + list_display = ["name", "melden_an_naturschutzbehoerde", "melden_an_jagdbehoerde", "melden_an_wildvogelhilfe_team"] + list_filter = ["melden_an_naturschutzbehoerde", "melden_an_jagdbehoerde", "melden_an_wildvogelhilfe_team"] + fields = ('name', 'description', 'melden_an_naturschutzbehoerde', 'melden_an_jagdbehoerde', 'melden_an_wildvogelhilfe_team') + + def save_model(self, request, obj, form, change): + if not change: # Only set created_by when creating new object + obj.created_by = request.user + super().save_model(request, obj, form, change) @admin.register(BirdStatus) diff --git a/app/bird/forms.py b/app/bird/forms.py index 9e18e39..381eea1 100644 --- a/app/bird/forms.py +++ b/app/bird/forms.py @@ -75,3 +75,30 @@ class BirdEditForm(forms.ModelForm): "finder": _("Finder"), "comment": _("Bermerkung"), } + + +class BirdSpeciesForm(forms.ModelForm): + """Form for editing Bird species with notification settings.""" + class Meta: + model = Bird + fields = [ + "name", + "description", + "species", + "melden_an_naturschutzbehoerde", + "melden_an_jagdbehoerde", + "melden_an_wildvogelhilfe_team", + ] + labels = { + "name": _("Bezeichnung"), + "description": _("Erläuterungen"), + "species": _("Art"), + "melden_an_naturschutzbehoerde": _("Melden an Naturschutzbehörde"), + "melden_an_jagdbehoerde": _("Melden an Jagdbehörde"), + "melden_an_wildvogelhilfe_team": _("Melden an Wildvogelhilfe-Team"), + } + help_texts = { + "melden_an_naturschutzbehoerde": _("Automatische E-Mail-Benachrichtigung an Naturschutzbehörde senden"), + "melden_an_jagdbehoerde": _("Automatische E-Mail-Benachrichtigung an Jagdbehörde senden"), + "melden_an_wildvogelhilfe_team": _("Automatische E-Mail-Benachrichtigung an Wildvogelhilfe-Team senden"), + } diff --git a/app/bird/migrations/0007_add_notification_settings.py b/app/bird/migrations/0007_add_notification_settings.py new file mode 100644 index 0000000..5b5a7c6 --- /dev/null +++ b/app/bird/migrations/0007_add_notification_settings.py @@ -0,0 +1,28 @@ +# Generated manually for notification settings + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bird', '0006_alter_fallenbird_options_alter_fallenbird_age_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='bird', + name='melden_an_naturschutzbehoerde', + field=models.BooleanField(default=True, verbose_name='Melden an Naturschutzbehörde'), + ), + migrations.AddField( + model_name='bird', + name='melden_an_jagdbehoerde', + field=models.BooleanField(default=False, verbose_name='Melden an Jagdbehörde'), + ), + migrations.AddField( + model_name='bird', + name='melden_an_wildvogelhilfe_team', + field=models.BooleanField(default=True, verbose_name='Melden an Wildvogelhilfe-Team'), + ), + ] diff --git a/app/bird/migrations/0008_set_default_notification_settings.py b/app/bird/migrations/0008_set_default_notification_settings.py new file mode 100644 index 0000000..df251ab --- /dev/null +++ b/app/bird/migrations/0008_set_default_notification_settings.py @@ -0,0 +1,41 @@ +# Data migration to set defaults for existing Bird records + +from django.db import migrations + + +def set_default_notification_settings(apps, schema_editor): + """Set default notification settings for all existing Bird records.""" + Bird = apps.get_model('bird', 'Bird') + + # Update all existing birds to have the default notification settings + Bird.objects.all().update( + melden_an_naturschutzbehoerde=True, + melden_an_wildvogelhilfe_team=True, + melden_an_jagdbehoerde=False + ) + + +def reverse_default_notification_settings(apps, schema_editor): + """Reverse the default settings if needed.""" + Bird = apps.get_model('bird', 'Bird') + + # Reset all notification settings to False + Bird.objects.all().update( + melden_an_naturschutzbehoerde=False, + melden_an_wildvogelhilfe_team=False, + melden_an_jagdbehoerde=False + ) + + +class Migration(migrations.Migration): + + dependencies = [ + ('bird', '0007_add_notification_settings'), + ] + + operations = [ + migrations.RunPython( + set_default_notification_settings, + reverse_default_notification_settings + ), + ] diff --git a/app/bird/migrations/0009_merge_20250609_2033.py b/app/bird/migrations/0009_merge_20250609_2033.py new file mode 100644 index 0000000..138edcb --- /dev/null +++ b/app/bird/migrations/0009_merge_20250609_2033.py @@ -0,0 +1,14 @@ +# Generated by Django 5.2.2 on 2025-06-09 18:33 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('bird', '0007_alter_fallenbird_status'), + ('bird', '0008_set_default_notification_settings'), + ] + + operations = [ + ] diff --git a/app/bird/models.py b/app/bird/models.py index 3ab0c19..d55e79d 100644 --- a/app/bird/models.py +++ b/app/bird/models.py @@ -193,6 +193,20 @@ class Bird(models.Model): updated = models.DateTimeField( auto_now=True, verbose_name=_("Geändert am") ) + + # New notification settings fields - "Melden an" section + melden_an_naturschutzbehoerde = models.BooleanField( + default=True, + verbose_name=_("Melden an Naturschutzbehörde") + ) + melden_an_jagdbehoerde = models.BooleanField( + default=False, + verbose_name=_("Melden an Jagdbehörde") + ) + melden_an_wildvogelhilfe_team = models.BooleanField( + default=True, + verbose_name=_("Melden an Wildvogelhilfe-Team") + ) class Meta: verbose_name = _("Vogel") diff --git a/app/bird/templates/bird/bird_species_edit.html b/app/bird/templates/bird/bird_species_edit.html new file mode 100644 index 0000000..d707c84 --- /dev/null +++ b/app/bird/templates/bird/bird_species_edit.html @@ -0,0 +1,50 @@ +{% extends "base.html" %} +{% load static %} +{% load crispy_forms_tags %} +{% block content %} + +

E-Mail-Benachrichtigungen für {{ bird_species.name }} bearbeiten

+
+
+
+ {% csrf_token %} + {{ form|crispy }} + Abbrechen + +
+
+ +
+
+
+
Informationen zu E-Mail-Benachrichtigungen
+
+
+
Naturschutzbehörde
+

+ Wenn aktiviert, wird automatisch eine E-Mail an alle als "Naturschutzbehörde" + markierten E-Mail-Adressen gesendet, wenn ein Vogel dieser Art gefunden wird. +

+ +
Jagdbehörde
+

+ Wenn aktiviert, wird automatisch eine E-Mail an alle als "Jagdbehörde" + markierten E-Mail-Adressen gesendet, wenn ein Vogel dieser Art gefunden wird. +

+ +
Wildvogelhilfe-Team
+

+ Wenn aktiviert, wird automatisch eine E-Mail an alle als "Wildvogelhilfe-Team" + markierten E-Mail-Adressen gesendet, wenn ein Vogel dieser Art gefunden wird. +

+ +
+ Hinweis: Für neue Vogelarten werden standardmäßig + "Naturschutzbehörde" und "Wildvogelhilfe-Team" aktiviert. +
+
+
+
+
+ +{% endblock content %} diff --git a/app/bird/templates/bird/bird_species_list.html b/app/bird/templates/bird/bird_species_list.html new file mode 100644 index 0000000..b41b9ec --- /dev/null +++ b/app/bird/templates/bird/bird_species_list.html @@ -0,0 +1,58 @@ +{% extends "base.html" %} +{% load static %} +{% block content %} + +

Vogelarten - E-Mail-Benachrichtigungen verwalten

+
+
+

+ Hier können Sie für jede Vogelart konfigurieren, welche Behörden und Teams + automatisch benachrichtigt werden sollen, wenn ein Vogel dieser Art gefunden wird. +

+ + + + + + + + + + + + + {% for bird in birds %} + + + + + + + + {% endfor %} + +
VogelartNaturschutzbehördeJagdbehördeWildvogelhilfe-TeamAktionen
{{ bird.name }} + {% if bird.melden_an_naturschutzbehoerde %} + Aktiv + {% else %} + Inaktiv + {% endif %} + + {% if bird.melden_an_jagdbehoerde %} + Aktiv + {% else %} + Inaktiv + {% endif %} + + {% if bird.melden_an_wildvogelhilfe_team %} + Aktiv + {% else %} + Inaktiv + {% endif %} + + Bearbeiten +
+
+
+ +{% endblock content %} diff --git a/app/bird/urls.py b/app/bird/urls.py index 6dff3a9..28353bc 100644 --- a/app/bird/urls.py +++ b/app/bird/urls.py @@ -8,6 +8,8 @@ from .views import ( bird_help_single, bird_inactive, bird_single, + bird_species_list, + bird_species_edit, ) urlpatterns = [ @@ -17,5 +19,7 @@ urlpatterns = [ path("delete/", bird_delete, name="bird_delete"), path("help/", bird_help, name="bird_help"), path("help/", bird_help_single, name="bird_help_single"), + path("species/", bird_species_list, name="bird_species_list"), + path("species//edit/", bird_species_edit, name="bird_species_edit"), path("/", bird_single, name="bird_single"), ] diff --git a/app/bird/views.py b/app/bird/views.py index ed5edb4..120bb87 100644 --- a/app/bird/views.py +++ b/app/bird/views.py @@ -7,11 +7,11 @@ from django.shortcuts import redirect, render, HttpResponse from django.core.mail import send_mail, BadHeaderError from smtplib import SMTPException -from .forms import BirdAddForm, BirdEditForm +from .forms import BirdAddForm, BirdEditForm, BirdSpeciesForm from .models import Bird, FallenBird from sendemail.message import messagebody -from sendemail.models import BirdEmail +from sendemail.models import BirdEmail, Emailadress env = environ.Env() @@ -33,24 +33,42 @@ def bird_create(request): fs.save() request.session["rescuer_id"] = None - # Send email to all related email addresses - email_addresses = BirdEmail.objects.filter(bird=fs.bird_id) + # Send email to all related email addresses based on bird species notification settings bird = Bird.objects.get(id=fs.bird_id) - try: - send_mail( - subject="Wildvogel gefunden!", - message=messagebody( - fs.date_found, bird, fs.place, fs.diagnostic_finding - ), - from_email=env("DEFAULT_FROM_EMAIL"), - recipient_list=[ - email.email.email_address for email in email_addresses - ], - ) - except BadHeaderError: - return HttpResponse("Invalid header found.") - except SMTPException as e: - print("There was an error sending an email: ", e) + + # Get email addresses that match the bird species' notification settings + email_addresses = [] + + # Check each notification category and add matching email addresses + if bird.melden_an_naturschutzbehoerde: + naturschutz_emails = Emailadress.objects.filter(is_naturschutzbehoerde=True) + email_addresses.extend([email.email_address for email in naturschutz_emails]) + + if bird.melden_an_jagdbehoerde: + jagd_emails = Emailadress.objects.filter(is_jagdbehoerde=True) + email_addresses.extend([email.email_address for email in jagd_emails]) + + if bird.melden_an_wildvogelhilfe_team: + team_emails = Emailadress.objects.filter(is_wildvogelhilfe_team=True) + email_addresses.extend([email.email_address for email in team_emails]) + + # Remove duplicates + email_addresses = list(set(email_addresses)) + + if email_addresses: # Only send if there are recipients + try: + send_mail( + subject="Wildvogel gefunden!", + message=messagebody( + fs.date_found, bird, fs.place, fs.diagnostic_finding + ), + from_email=env("DEFAULT_FROM_EMAIL"), + recipient_list=email_addresses, + ) + except BadHeaderError: + return HttpResponse("Invalid header found.") + except SMTPException as e: + print("There was an error sending an email: ", e) return redirect("bird_all") context = {"form": form} @@ -119,3 +137,26 @@ def bird_delete(request, id): return redirect("bird_all") context = {"bird": bird} return render(request, "bird/bird_delete.html", context) + + +@login_required(login_url="account_login") +def bird_species_list(request): + """List all bird species with their notification settings.""" + birds = Bird.objects.all().order_by("name") + context = {"birds": birds} + return render(request, "bird/bird_species_list.html", context) + + +@login_required(login_url="account_login") +def bird_species_edit(request, id): + """Edit bird species notification settings.""" + bird_species = Bird.objects.get(id=id) + form = BirdSpeciesForm(request.POST or None, instance=bird_species) + + if request.method == "POST": + if form.is_valid(): + form.save() + return redirect("bird_species_list") + + context = {"form": form, "bird_species": bird_species} + return render(request, "bird/bird_species_edit.html", context) diff --git a/app/core/settings.py b/app/core/settings.py index 3a45a7d..c3f2e0d 100644 --- a/app/core/settings.py +++ b/app/core/settings.py @@ -233,6 +233,12 @@ STATIC_URL = "static/" STATICFILES_DIRS = [BASE_DIR / "static"] STATIC_ROOT = BASE_DIR / "staticfiles" +# ----------------------------------- +# Media files (User uploaded content) +# ----------------------------------- +MEDIA_URL = "/media/" +MEDIA_ROOT = BASE_DIR / "media" + # ----------------------------------- # Email # ----------------------------------- diff --git a/app/core/urls.py b/app/core/urls.py index 08dee03..68dba37 100644 --- a/app/core/urls.py +++ b/app/core/urls.py @@ -1,5 +1,7 @@ from django.contrib import admin from django.urls import path, include +from django.conf import settings +from django.conf.urls.static import static from bird import views urlpatterns = [ @@ -14,6 +16,12 @@ urlpatterns = [ path("admin/", admin.site.urls), # Allauth path("accounts/", include("allauth.urls")), + # CKEditor 5 + path("ckeditor5/", include('django_ckeditor_5.urls')), # Static sites # path("", include("sites.urls")), ] + +# Serve media files during development +if settings.DEBUG: + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/app/sendemail/admin.py b/app/sendemail/admin.py index 59bb84a..fdea9b3 100644 --- a/app/sendemail/admin.py +++ b/app/sendemail/admin.py @@ -5,10 +5,24 @@ from .models import Emailadress, BirdEmail @admin.register(Emailadress) class EmailaddressAdmin(admin.ModelAdmin): - list_display = ["email_address", "created_at", "updated_at", "user"] + list_display = ["email_address", "is_naturschutzbehoerde", "is_jagdbehoerde", "is_wildvogelhilfe_team", "created_at", "updated_at", "user"] search_fields = ["email_address"] - list_filter = ["created_at", "updated_at", "user"] + list_filter = ["is_naturschutzbehoerde", "is_jagdbehoerde", "is_wildvogelhilfe_team", "created_at", "updated_at", "user"] list_per_page = 20 + fieldsets = ( + (None, { + 'fields': ('email_address',) + }), + ('Notification Categories', { + 'fields': ('is_naturschutzbehoerde', 'is_jagdbehoerde', 'is_wildvogelhilfe_team'), + 'description': 'Select which types of notifications this email address should receive' + }), + ) + + def save_model(self, request, obj, form, change): + if not change: # Only set user when creating new object + obj.user = request.user + super().save_model(request, obj, form, change) @admin.register(BirdEmail) diff --git a/app/sendemail/forms.py b/app/sendemail/forms.py new file mode 100644 index 0000000..fde77b6 --- /dev/null +++ b/app/sendemail/forms.py @@ -0,0 +1,30 @@ +from django import forms +from django.utils.translation import gettext_lazy as _ + +from .models import Emailadress + + +class EmailaddressForm(forms.ModelForm): + """Form for editing email addresses with notification categories.""" + class Meta: + model = Emailadress + fields = [ + "email_address", + "is_naturschutzbehoerde", + "is_jagdbehoerde", + "is_wildvogelhilfe_team", + ] + labels = { + "email_address": _("E-Mail-Adresse"), + "is_naturschutzbehoerde": _("Naturschutzbehörde"), + "is_jagdbehoerde": _("Jagdbehörde"), + "is_wildvogelhilfe_team": _("Wildvogelhilfe-Team"), + } + help_texts = { + "is_naturschutzbehoerde": _("Diese Adresse für Naturschutzbehörden-Benachrichtigungen verwenden"), + "is_jagdbehoerde": _("Diese Adresse für Jagdbehörden-Benachrichtigungen verwenden"), + "is_wildvogelhilfe_team": _("Diese Adresse für Wildvogelhilfe-Team-Benachrichtigungen verwenden"), + } + widgets = { + "email_address": forms.EmailInput(attrs={"class": "form-control"}), + } diff --git a/app/sendemail/migrations/0002_add_notification_categories.py b/app/sendemail/migrations/0002_add_notification_categories.py new file mode 100644 index 0000000..a898852 --- /dev/null +++ b/app/sendemail/migrations/0002_add_notification_categories.py @@ -0,0 +1,28 @@ +# Generated manually for notification categories + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sendemail', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='emailadress', + name='is_naturschutzbehoerde', + field=models.BooleanField(default=False, verbose_name='Naturschutzbehörde'), + ), + migrations.AddField( + model_name='emailadress', + name='is_jagdbehoerde', + field=models.BooleanField(default=False, verbose_name='Jagdbehörde'), + ), + migrations.AddField( + model_name='emailadress', + name='is_wildvogelhilfe_team', + field=models.BooleanField(default=False, verbose_name='Wildvogelhilfe-Team'), + ), + ] diff --git a/app/sendemail/migrations/0003_alter_emailadress_is_naturschutzbehoerde_and_more.py b/app/sendemail/migrations/0003_alter_emailadress_is_naturschutzbehoerde_and_more.py new file mode 100644 index 0000000..ded3a26 --- /dev/null +++ b/app/sendemail/migrations/0003_alter_emailadress_is_naturschutzbehoerde_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2.2 on 2025-06-10 06:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sendemail', '0002_add_notification_categories'), + ] + + operations = [ + migrations.AlterField( + model_name='emailadress', + name='is_naturschutzbehoerde', + field=models.BooleanField(default=True, verbose_name='Naturschutzbehörde'), + ), + migrations.AlterField( + model_name='emailadress', + name='is_wildvogelhilfe_team', + field=models.BooleanField(default=True, verbose_name='Wildvogelhilfe-Team'), + ), + ] diff --git a/app/sendemail/models.py b/app/sendemail/models.py index 464b6d3..91acf5b 100644 --- a/app/sendemail/models.py +++ b/app/sendemail/models.py @@ -14,6 +14,20 @@ class Emailadress(models.Model): on_delete=models.CASCADE, verbose_name=_("Benutzer"), ) + + # New notification category fields + is_naturschutzbehoerde = models.BooleanField( + default=True, + verbose_name=_("Naturschutzbehörde") + ) + is_jagdbehoerde = models.BooleanField( + default=False, + verbose_name=_("Jagdbehörde") + ) + is_wildvogelhilfe_team = models.BooleanField( + default=True, + verbose_name=_("Wildvogelhilfe-Team") + ) def __str__(self): return self.email_address diff --git a/app/templates/partials/_navbar.html b/app/templates/partials/_navbar.html index ca4c2da..43c891a 100644 --- a/app/templates/partials/_navbar.html +++ b/app/templates/partials/_navbar.html @@ -36,6 +36,10 @@ Kontakte + {% if request.user|group_check:"data-export" %}