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
+
+
+
+
+
+
+
+
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.
+
+
+
+
+
+ Vogelart |
+ Naturschutzbehörde |
+ Jagdbehörde |
+ Wildvogelhilfe-Team |
+ Aktionen |
+
+
+
+ {% for bird in birds %}
+
+ {{ 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
+ |
+
+ {% endfor %}
+
+
+
+
+
+{% 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
+
+ Vogelarten
+
{% if request.user|group_check:"data-export" %}
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 16eb608..ecde09d 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -31,7 +31,8 @@ services:
- db
labels:
- "traefik.enable=true"
- - "traefik.http.routers.django.rule=Host(`${ALLOWED_HOSTS}`)"
+ - "traefik.http.routers.web.rule=Host(`${ALLOWED_HOSTS}`)"
+ - "traefik.http.services.web.loadbalancer.server.port=8000"
db:
image: postgres:15-alpine