Merge branch 'notification_rework' into develop
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				ci/woodpecker/push/woodpecker Pipeline was successful
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	ci/woodpecker/push/woodpecker Pipeline was successful
				
			This commit is contained in:
		@@ -11,7 +11,7 @@ from .models import User, Language, Text, ReportComment, ReportAdoptionNotice, L
 | 
			
		||||
    SpeciesSpecificURL, ImportantLocation, SpeciesSpecialization
 | 
			
		||||
 | 
			
		||||
from .models import Animal, Species, RescueOrganization, AdoptionNotice, Location, Rule, Image, ModerationAction, \
 | 
			
		||||
    Comment, Report, Announcement, AdoptionNoticeStatus, User, Subscriptions, BaseNotification
 | 
			
		||||
    Comment, Report, Announcement, AdoptionNoticeStatus, User, Subscriptions, Notification
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -127,9 +127,9 @@ class CommentAdmin(admin.ModelAdmin):
 | 
			
		||||
    list_filter = ("user",)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@admin.register(BaseNotification)
 | 
			
		||||
@admin.register(Notification)
 | 
			
		||||
class BaseNotificationAdmin(admin.ModelAdmin):
 | 
			
		||||
    list_filter = ("user", "read")
 | 
			
		||||
    list_filter = ("user_to_notify", "read")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@admin.register(SearchSubscription)
 | 
			
		||||
 
 | 
			
		||||
@@ -66,22 +66,22 @@ class AdoptionNoticeApiView(APIView):
 | 
			
		||||
        if not serializer.is_valid():
 | 
			
		||||
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
 | 
			
		||||
 | 
			
		||||
        adoption_notice = serializer.save(owner=request.user)
 | 
			
		||||
        adoption_notice = serializer.save(owner=request.user_to_notify)
 | 
			
		||||
 | 
			
		||||
        # Add the location
 | 
			
		||||
        post_adoption_notice_save.delay_on_commit(adoption_notice.pk)
 | 
			
		||||
 | 
			
		||||
        # Only set active when user has trust level moderator or higher
 | 
			
		||||
        if request.user.trust_level >= TrustLevel.MODERATOR:
 | 
			
		||||
        if request.user_to_notify.trust_level >= TrustLevel.MODERATOR:
 | 
			
		||||
            adoption_notice.set_active()
 | 
			
		||||
        else:
 | 
			
		||||
            adoption_notice.set_unchecked()
 | 
			
		||||
 | 
			
		||||
        # Log the action
 | 
			
		||||
        Log.objects.create(
 | 
			
		||||
            user=request.user,
 | 
			
		||||
            user=request.user_to_notify,
 | 
			
		||||
            action="add_adoption_notice",
 | 
			
		||||
            text=f"{request.user} added adoption notice {adoption_notice.pk} via API",
 | 
			
		||||
            text=f"{request.user_to_notify} added adoption notice {adoption_notice.pk} via API",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Return success response with new adoption notice details
 | 
			
		||||
@@ -130,7 +130,7 @@ class AnimalApiView(APIView):
 | 
			
		||||
        """
 | 
			
		||||
        serializer = AnimalCreateSerializer(data=request.data, context={"request": request})
 | 
			
		||||
        if serializer.is_valid():
 | 
			
		||||
            animal = serializer.save(owner=request.user)
 | 
			
		||||
            animal = serializer.save(owner=request.user_to_notify)
 | 
			
		||||
            return Response(
 | 
			
		||||
                {"message": "Animal created successfully!", "id": animal.id},
 | 
			
		||||
                status=status.HTTP_201_CREATED,
 | 
			
		||||
@@ -289,7 +289,7 @@ class AddImageApiView(APIView):
 | 
			
		||||
                raise ValueError("Unknown attach_to_type given, should not happen. Check serializer")
 | 
			
		||||
            serializer.validated_data.pop('attach_to_type', None)
 | 
			
		||||
            serializer.validated_data.pop('attach_to', None)
 | 
			
		||||
            image = serializer.save(owner=request.user)
 | 
			
		||||
            image = serializer.save(owner=request.user_to_notify)
 | 
			
		||||
            object_to_attach_to.photos.add(image)
 | 
			
		||||
            return Response(
 | 
			
		||||
                {"message": "Image added successfully!", "id": image.id},
 | 
			
		||||
@@ -360,9 +360,9 @@ class LocationApiView(APIView):
 | 
			
		||||
 | 
			
		||||
        # Log the action
 | 
			
		||||
        Log.objects.create(
 | 
			
		||||
            user=request.user,
 | 
			
		||||
            user=request.user_to_notify,
 | 
			
		||||
            action="add_location",
 | 
			
		||||
            text=f"{request.user} added adoption notice {location.pk} via API",
 | 
			
		||||
            text=f"{request.user_to_notify} added adoption notice {location.pk} via API",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Return success response with new adoption notice details
 | 
			
		||||
 
 | 
			
		||||
@@ -6,8 +6,8 @@ from django.utils.html import strip_tags
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.core import mail
 | 
			
		||||
from fellchensammlung.models import User, CommentNotification, BaseNotification, TrustLevel
 | 
			
		||||
from notfellchen.settings import host
 | 
			
		||||
from fellchensammlung.models import User, Notification, TrustLevel, NotificationTypeChoices
 | 
			
		||||
from notfellchen.settings import base_url
 | 
			
		||||
 | 
			
		||||
NEWLINE = "\r\n"
 | 
			
		||||
 | 
			
		||||
@@ -17,12 +17,12 @@ def mail_admins_new_report(report):
 | 
			
		||||
    Sends an e-mail to all users that should handle the report.
 | 
			
		||||
    """
 | 
			
		||||
    for moderator in User.objects.filter(trust_level__gt=TrustLevel.MODERATOR):
 | 
			
		||||
        report_url = "https://" + host + report.get_absolute_url()
 | 
			
		||||
        report_url = base_url + report.get_absolute_url()
 | 
			
		||||
        context = {"report_url": report_url,
 | 
			
		||||
                   "user_comment": report.user_comment,}
 | 
			
		||||
                   "user_comment": report.user_comment, }
 | 
			
		||||
 | 
			
		||||
        subject = _("Neue Meldung")
 | 
			
		||||
        html_message = render_to_string('fellchensammlung/mail/report.html', context)
 | 
			
		||||
        html_message = render_to_string('fellchensammlung/mail/notifications/report.html', context)
 | 
			
		||||
        plain_message = strip_tags(html_message)
 | 
			
		||||
 | 
			
		||||
        mail.send_mail(subject,
 | 
			
		||||
@@ -33,11 +33,28 @@ def mail_admins_new_report(report):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def send_notification_email(notification_pk):
 | 
			
		||||
    try:
 | 
			
		||||
        notification = CommentNotification.objects.get(pk=notification_pk)
 | 
			
		||||
    except CommentNotification.DoesNotExist:
 | 
			
		||||
        notification = BaseNotification.objects.get(pk=notification_pk)
 | 
			
		||||
    subject = f"🔔 {notification.title}"
 | 
			
		||||
    body_text = notification.text
 | 
			
		||||
    message = mail.EmailMessage(subject, body_text, settings.DEFAULT_FROM_EMAIL, [notification.user.email])
 | 
			
		||||
    message.send()
 | 
			
		||||
    notification = Notification.objects.get(pk=notification_pk)
 | 
			
		||||
 | 
			
		||||
    subject = f"{notification.title}"
 | 
			
		||||
    context = {"notification": notification, }
 | 
			
		||||
    if notification.notification_type == NotificationTypeChoices.NEW_REPORT_COMMENT or notification.notification_type == NotificationTypeChoices.NEW_REPORT_AN:
 | 
			
		||||
        context["user_comment"] = notification.report.user_comment
 | 
			
		||||
        context["report_url"] = f"{base_url}{notification.report.get_absolute_url()}"
 | 
			
		||||
        html_message = render_to_string('fellchensammlung/mail/notifications/report.html', context)
 | 
			
		||||
    elif notification.notification_type == NotificationTypeChoices.NEW_USER:
 | 
			
		||||
        html_message = render_to_string('fellchensammlung/mail/notifications/new-user.html', context)
 | 
			
		||||
    elif notification.notification_type == NotificationTypeChoices.AN_IS_TO_BE_CHECKED:
 | 
			
		||||
        html_message = render_to_string('fellchensammlung/mail/notifications/an-to-be-checked.html', context)
 | 
			
		||||
    elif notification.notification_type == NotificationTypeChoices.AN_WAS_DEACTIVATED:
 | 
			
		||||
        html_message = render_to_string('fellchensammlung/mail/notifications/an-deactivated.html', context)
 | 
			
		||||
    elif notification.notification_type == NotificationTypeChoices.AN_FOR_SEARCH_FOUND:
 | 
			
		||||
        html_message = render_to_string('fellchensammlung/mail/notifications/an-for-search-found.html', context)
 | 
			
		||||
    elif notification.notification_type == NotificationTypeChoices.NEW_COMMENT:
 | 
			
		||||
        html_message = render_to_string('fellchensammlung/mail/notifications/new-comment.html', context)
 | 
			
		||||
    else:
 | 
			
		||||
        raise NotImplementedError("Unknown notification type")
 | 
			
		||||
 | 
			
		||||
    plain_message = strip_tags(html_message)
 | 
			
		||||
    mail.send_mail(subject, plain_message, settings.DEFAULT_FROM_EMAIL,
 | 
			
		||||
                   [notification.user_to_notify.email],
 | 
			
		||||
                   html_message=html_message)
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,54 @@
 | 
			
		||||
# Generated by Django 5.2.1 on 2025-07-11 09:01
 | 
			
		||||
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('fellchensammlung', '0051_rescueorganization_parent_org_speciesspecialization'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.RemoveField(
 | 
			
		||||
            model_name='basenotification',
 | 
			
		||||
            name='user',
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.RemoveField(
 | 
			
		||||
            model_name='commentnotification',
 | 
			
		||||
            name='basenotification_ptr',
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.RemoveField(
 | 
			
		||||
            model_name='commentnotification',
 | 
			
		||||
            name='comment',
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='Notification',
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 | 
			
		||||
                ('created_at', models.DateTimeField(auto_now_add=True)),
 | 
			
		||||
                ('updated_at', models.DateTimeField(auto_now=True)),
 | 
			
		||||
                ('read_at', models.DateTimeField(blank=True, null=True, verbose_name='Gelesen am')),
 | 
			
		||||
                ('notification_type', models.CharField(choices=[('new_user', 'Useraccount wurde erstellt'), ('new_report_an', 'Vermittlung wurde gemeldet'), ('new_report_comment', 'Kommentar wurde gemeldet'), ('an_is_to_be_checked', 'Vermittlung muss überprüft werden'), ('an_was_deactivated', 'Vermittlung wurde deaktiviert'), ('an_for_search_found', 'Vermittlung für Suche gefunden')], max_length=200, verbose_name='Benachrichtigungsgrund')),
 | 
			
		||||
                ('title', models.CharField(max_length=100, verbose_name='Titel')),
 | 
			
		||||
                ('text', models.TextField(verbose_name='Inhalt')),
 | 
			
		||||
                ('read', models.BooleanField(default=False)),
 | 
			
		||||
                ('adoption_notice', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.adoptionnotice', verbose_name='Vermittlung')),
 | 
			
		||||
                ('comment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.comment', verbose_name='Antwort')),
 | 
			
		||||
                ('report', models.ForeignKey(help_text='Report auf den sich die Benachrichtigung bezieht.', on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.report', verbose_name='Report')),
 | 
			
		||||
                ('user_related', models.ForeignKey(help_text='Useraccount auf den sich die Benachrichtigung bezieht.', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Verwandter Useraccount')),
 | 
			
		||||
                ('user_to_notify', models.ForeignKey(help_text='Useraccount der Benachrichtigt wird', on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL, verbose_name='Nutzer*in')),
 | 
			
		||||
            ],
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.DeleteModel(
 | 
			
		||||
            name='AdoptionNoticeNotification',
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.DeleteModel(
 | 
			
		||||
            name='BaseNotification',
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.DeleteModel(
 | 
			
		||||
            name='CommentNotification',
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@@ -0,0 +1,40 @@
 | 
			
		||||
# Generated by Django 5.2.1 on 2025-07-11 09:10
 | 
			
		||||
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('fellchensammlung', '0052_remove_basenotification_user_and_more'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='notification',
 | 
			
		||||
            name='adoption_notice',
 | 
			
		||||
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.adoptionnotice', verbose_name='Vermittlung'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='notification',
 | 
			
		||||
            name='notification_type',
 | 
			
		||||
            field=models.CharField(choices=[('new_user', 'Useraccount wurde erstellt'), ('new_report_an', 'Vermittlung wurde gemeldet'), ('new_report_comment', 'Kommentar wurde gemeldet'), ('an_is_to_be_checked', 'Vermittlung muss überprüft werden'), ('an_was_deactivated', 'Vermittlung wurde deaktiviert'), ('an_for_search_found', 'Vermittlung für Suche gefunden'), ('new_comment', 'Neuer Kommentar')], max_length=200, verbose_name='Benachrichtigungsgrund'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='notification',
 | 
			
		||||
            name='report',
 | 
			
		||||
            field=models.ForeignKey(blank=True, help_text='Report auf den sich die Benachrichtigung bezieht.', null=True, on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.report', verbose_name='Report'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='notification',
 | 
			
		||||
            name='user_related',
 | 
			
		||||
            field=models.ForeignKey(blank=True, help_text='Useraccount auf den sich die Benachrichtigung bezieht.', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Verwandter Useraccount'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='notification',
 | 
			
		||||
            name='user_to_notify',
 | 
			
		||||
            field=models.ForeignKey(help_text='Useraccount der Benachrichtigt wird', on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL, verbose_name='Nutzer*in'),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@@ -0,0 +1,19 @@
 | 
			
		||||
# Generated by Django 5.2.1 on 2025-07-11 11:47
 | 
			
		||||
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('fellchensammlung', '0053_alter_notification_adoption_notice_and_more'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='notification',
 | 
			
		||||
            name='comment',
 | 
			
		||||
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.comment', verbose_name='Antwort'),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@@ -14,7 +14,7 @@ from django.contrib.auth.models import AbstractUser
 | 
			
		||||
from django.core.exceptions import ValidationError
 | 
			
		||||
 | 
			
		||||
from .tools import misc, geo
 | 
			
		||||
from notfellchen.settings import MEDIA_URL
 | 
			
		||||
from notfellchen.settings import MEDIA_URL, base_url
 | 
			
		||||
from .tools.geo import LocationProxy, Position
 | 
			
		||||
from .tools.misc import age_as_hr_string, time_since_as_hr_string
 | 
			
		||||
 | 
			
		||||
@@ -256,10 +256,10 @@ class User(AbstractUser):
 | 
			
		||||
        return self.get_absolute_url()
 | 
			
		||||
 | 
			
		||||
    def get_unread_notifications(self):
 | 
			
		||||
        return BaseNotification.objects.filter(user=self, read=False)
 | 
			
		||||
        return Notification.objects.filter(user=self, read=False)
 | 
			
		||||
 | 
			
		||||
    def get_num_unread_notifications(self):
 | 
			
		||||
        return BaseNotification.objects.filter(user=self, read=False).count()
 | 
			
		||||
        return Notification.objects.filter(user=self, read=False).count()
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def adoption_notices(self):
 | 
			
		||||
@@ -389,6 +389,10 @@ class AdoptionNotice(models.Model):
 | 
			
		||||
        """Returns the url to access a detailed page for the adoption notice."""
 | 
			
		||||
        return reverse('adoption-notice-detail', args=[str(self.id)])
 | 
			
		||||
 | 
			
		||||
    def get_full_url(self):
 | 
			
		||||
        """Returns the url including protocol and domain"""
 | 
			
		||||
        return f"{base_url}{self.get_absolute_url()}"
 | 
			
		||||
 | 
			
		||||
    def get_report_url(self):
 | 
			
		||||
        """Returns the url to report an adoption notice."""
 | 
			
		||||
        return reverse('report-adoption-notice', args=[str(self.id)])
 | 
			
		||||
@@ -479,7 +483,11 @@ class AdoptionNotice(models.Model):
 | 
			
		||||
        for subscription in self.get_subscriptions():
 | 
			
		||||
            notification_title = _("Vermittlung deaktiviert:") + f" {self.name}"
 | 
			
		||||
            text = _("Die folgende Vermittlung wurde deaktiviert: ") + f"[{self.name}]({self.get_absolute_url()})"
 | 
			
		||||
            BaseNotification.objects.create(user=subscription.owner, text=text, title=notification_title)
 | 
			
		||||
            Notification.objects.create(user_to_notify=subscription.owner,
 | 
			
		||||
                                        notification_type=NotificationTypeChoices.AN_WAS_DEACTIVATED,
 | 
			
		||||
                                        adoption_notice=self,
 | 
			
		||||
                                        text=text,
 | 
			
		||||
                                        title=notification_title)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AdoptionNoticeStatus(models.Model):
 | 
			
		||||
@@ -902,20 +910,48 @@ class Comment(models.Model):
 | 
			
		||||
        return self.adoption_notice.get_absolute_url()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseNotification(models.Model):
 | 
			
		||||
class NotificationTypeChoices(models.TextChoices):
 | 
			
		||||
    NEW_USER = "new_user", _("Useraccount wurde erstellt")
 | 
			
		||||
    NEW_REPORT_AN = "new_report_an", _("Vermittlung wurde gemeldet")
 | 
			
		||||
    NEW_REPORT_COMMENT = "new_report_comment", _("Kommentar wurde gemeldet")
 | 
			
		||||
    AN_IS_TO_BE_CHECKED = "an_is_to_be_checked", _("Vermittlung muss überprüft werden")
 | 
			
		||||
    AN_WAS_DEACTIVATED = "an_was_deactivated", _("Vermittlung wurde deaktiviert")
 | 
			
		||||
    AN_FOR_SEARCH_FOUND = "an_for_search_found", _("Vermittlung für Suche gefunden")
 | 
			
		||||
    NEW_COMMENT = "new_comment", _("Neuer Kommentar")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Notification(models.Model):
 | 
			
		||||
    created_at = models.DateTimeField(auto_now_add=True)
 | 
			
		||||
    updated_at = models.DateTimeField(auto_now=True)
 | 
			
		||||
    read_at = models.DateTimeField(blank=True, null=True, verbose_name=_("Gelesen am"))
 | 
			
		||||
    notification_type = models.CharField(max_length=200,
 | 
			
		||||
                                         choices=NotificationTypeChoices.choices,
 | 
			
		||||
                                         verbose_name=_('Benachrichtigungsgrund'))
 | 
			
		||||
    title = models.CharField(max_length=100, verbose_name=_("Titel"))
 | 
			
		||||
    text = models.TextField(verbose_name="Inhalt")
 | 
			
		||||
    user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('Nutzer*in'))
 | 
			
		||||
    user_to_notify = models.ForeignKey(User,
 | 
			
		||||
                                       on_delete=models.CASCADE,
 | 
			
		||||
                                       verbose_name=_('Nutzer*in'),
 | 
			
		||||
                                       help_text=_("Useraccount der Benachrichtigt wird"),
 | 
			
		||||
                                       related_name='user')
 | 
			
		||||
    read = models.BooleanField(default=False)
 | 
			
		||||
    comment = models.ForeignKey(Comment, blank=True, null=True, on_delete=models.CASCADE, verbose_name=_('Antwort'))
 | 
			
		||||
    adoption_notice = models.ForeignKey(AdoptionNotice, blank=True, null=True, on_delete=models.CASCADE, verbose_name=_('Vermittlung'))
 | 
			
		||||
    user_related = models.ForeignKey(User,
 | 
			
		||||
                                     blank=True, null=True,
 | 
			
		||||
                                     on_delete=models.CASCADE, verbose_name=_('Verwandter Useraccount'),
 | 
			
		||||
                                     help_text=_("Useraccount auf den sich die Benachrichtigung bezieht."))
 | 
			
		||||
    report = models.ForeignKey(Report,
 | 
			
		||||
                               blank=True, null=True,
 | 
			
		||||
                               on_delete=models.CASCADE,
 | 
			
		||||
                               verbose_name=_('Report'),
 | 
			
		||||
                               help_text=_("Report auf den sich die Benachrichtigung bezieht."))
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return f"[{self.user}] {self.title} ({self.created_at})"
 | 
			
		||||
        return f"[{self.user_to_notify}] {self.title} ({self.created_at})"
 | 
			
		||||
 | 
			
		||||
    def get_absolute_url(self):
 | 
			
		||||
        self.user.get_notifications_url()
 | 
			
		||||
        self.user_to_notify.get_notifications_url()
 | 
			
		||||
 | 
			
		||||
    def mark_read(self):
 | 
			
		||||
        self.read = True
 | 
			
		||||
@@ -923,22 +959,6 @@ class BaseNotification(models.Model):
 | 
			
		||||
        self.save()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CommentNotification(BaseNotification):
 | 
			
		||||
    comment = models.ForeignKey(Comment, on_delete=models.CASCADE, verbose_name=_('Antwort'))
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def url(self):
 | 
			
		||||
        return self.comment.get_absolute_url
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AdoptionNoticeNotification(BaseNotification):
 | 
			
		||||
    adoption_notice = models.ForeignKey(AdoptionNotice, on_delete=models.CASCADE, verbose_name=_('Vermittlung'))
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def url(self):
 | 
			
		||||
        return self.adoption_notice.get_absolute_url
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Subscriptions(models.Model):
 | 
			
		||||
    owner = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('Nutzer*in'))
 | 
			
		||||
    adoption_notice = models.ForeignKey(AdoptionNotice, on_delete=models.CASCADE, verbose_name=_('AdoptionNotice'))
 | 
			
		||||
 
 | 
			
		||||
@@ -1,23 +1,20 @@
 | 
			
		||||
from django.db.models.signals import post_save
 | 
			
		||||
from django.dispatch import receiver
 | 
			
		||||
from fellchensammlung.models import BaseNotification, CommentNotification, User, TrustLevel, RescueOrganization
 | 
			
		||||
from fellchensammlung.models import Notification, User, TrustLevel, RescueOrganization, \
 | 
			
		||||
    NotificationTypeChoices
 | 
			
		||||
from .tasks import task_send_notification_email
 | 
			
		||||
from notfellchen.settings import host
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@receiver(post_save, sender=CommentNotification)
 | 
			
		||||
def comment_notification_receiver(sender, instance: BaseNotification, created: bool, **kwargs):
 | 
			
		||||
    base_notification_receiver(sender, instance, created, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@receiver(post_save, sender=BaseNotification)
 | 
			
		||||
def base_notification_receiver(sender, instance: BaseNotification, created: bool, **kwargs):
 | 
			
		||||
    if not created or not instance.user.email_notifications:
 | 
			
		||||
@receiver(post_save, sender=Notification)
 | 
			
		||||
def base_notification_receiver(sender, instance: Notification, created: bool, **kwargs):
 | 
			
		||||
    if not created or not instance.user_to_notify.email_notifications:
 | 
			
		||||
        return
 | 
			
		||||
    else:
 | 
			
		||||
        task_send_notification_email.delay(instance.pk)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@receiver(post_save, sender=RescueOrganization)
 | 
			
		||||
def rescue_org_receiver(sender, instance: RescueOrganization, created: bool, **kwargs):
 | 
			
		||||
    if instance.location:
 | 
			
		||||
@@ -40,5 +37,9 @@ def notification_new_user(sender, instance: User, created: bool, **kwargs):
 | 
			
		||||
    link_text = f"Um alle Details zu sehen, geh bitte auf: {user_url}"
 | 
			
		||||
    body_text = new_user_text + user_detail_text + link_text
 | 
			
		||||
    for moderator in User.objects.filter(trust_level__gt=TrustLevel.MODERATOR):
 | 
			
		||||
        notification = BaseNotification.objects.create(title=subject, text=body_text, user=moderator)
 | 
			
		||||
        notification = Notification.objects.create(title=subject,
 | 
			
		||||
                                                   text=body_text,
 | 
			
		||||
                                                   notification_type=NotificationTypeChoices.NEW_USER,
 | 
			
		||||
                                                   user_to_notify=moderator,
 | 
			
		||||
                                                   user_related=instance)
 | 
			
		||||
        notification.save()
 | 
			
		||||
 
 | 
			
		||||
@@ -48,6 +48,7 @@ def post_adoption_notice_save(pk):
 | 
			
		||||
    notify_search_subscribers(instance, only_if_active=True)
 | 
			
		||||
    notify_of_AN_to_be_checked(instance)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@celery_app.task(name="tools.healthcheck")
 | 
			
		||||
def task_healthcheck():
 | 
			
		||||
    healthcheck_ok()
 | 
			
		||||
@@ -58,6 +59,7 @@ def task_healthcheck():
 | 
			
		||||
def task_send_notification_email(notification_pk):
 | 
			
		||||
    send_notification_email(notification_pk)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@celery_app.task(name="commit.post_rescue_org_save")
 | 
			
		||||
def post_rescue_org_save(pk):
 | 
			
		||||
    instance = RescueOrganization.objects.get(pk=pk)
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,16 @@
 | 
			
		||||
{% extends "fellchensammlung/mail/base.html" %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
{% block title %}
 | 
			
		||||
    {% translate 'Vermittlung wurde deaktiviert' %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <p>Moin,</p>
 | 
			
		||||
    <p>
 | 
			
		||||
        die Vermittlung {{ notification.adoption_notice }} wurde deaktiviert.
 | 
			
		||||
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        <a href="{{ notification.adoption_notice.get_full_url }}" class="cta-button">{% translate 'Vermittlung anzeigen' %}</a>
 | 
			
		||||
    </p>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -0,0 +1,15 @@
 | 
			
		||||
{% extends "fellchensammlung/mail/base.html" %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
{% block title %}
 | 
			
		||||
    {% translate 'Neue Vermittlung gefunden' %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <p>Moin,</p>
 | 
			
		||||
    <p>
 | 
			
		||||
        es wurde eine neue Vermittlung gefunden die deinen Kriterien entspricht: {{ notification.adoption_notice }}
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        <a href="{{ notification.adoption_notice.get_full_url }}" class="cta-button">{% translate 'Vermittlung anzeigen' %}</a>
 | 
			
		||||
    </p>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -0,0 +1,15 @@
 | 
			
		||||
{% extends "fellchensammlung/mail/base.html" %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
{% block title %}
 | 
			
		||||
    {% translate 'Vermittlung muss überprüft werden' %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <p>Moin,</p>
 | 
			
		||||
    <p>
 | 
			
		||||
        die Vermittlung {{ notification.adoption_notice }} muss überprüft werden.
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        <a href="{{ notification.adoption_notice.get_full_url }}" class="cta-button">{% translate 'Vermittlung anzeigen' %}</a>
 | 
			
		||||
    </p>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -0,0 +1,19 @@
 | 
			
		||||
{% extends "fellchensammlung/mail/base.html" %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
{% load custom_tags %}
 | 
			
		||||
{% block title %}
 | 
			
		||||
    {% translate 'Vermittlung muss überprüft werden' %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <p>Moin,</p>
 | 
			
		||||
    <p>
 | 
			
		||||
        folgender Kommentar wurde zur Vermittlung {{ notification.adoption_notice }} hinzugefügt:
 | 
			
		||||
    </p>
 | 
			
		||||
    <p><i>
 | 
			
		||||
        {{ notification.comment.text | render_markdown }}
 | 
			
		||||
    </i></p>
 | 
			
		||||
    <p>
 | 
			
		||||
        <a href="{{ notification.adoption_notice.get_full_url }}" class="cta-button">{% translate 'Vermittlung anzeigen' %}</a>
 | 
			
		||||
    </p>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -0,0 +1,19 @@
 | 
			
		||||
{% extends "fellchensammlung/mail/base.html" %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
{% block title %}
 | 
			
		||||
    {% translate 'Neuer User' %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <p>Moin,</p>
 | 
			
		||||
    <p>
 | 
			
		||||
        es wurde ein neuer Useraccount erstellt.
 | 
			
		||||
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        Details findest du hier
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        <a href="{{ notification.user_related.get_absolute_url }}" class="cta-button">{% translate 'User anzeigen' %}</a>
 | 
			
		||||
    </p>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <p>Moin,</p>
 | 
			
		||||
    <p>
 | 
			
		||||
        es gibt eine neue Meldung. Folgende Nachricht wurde zur Meldung hinzugefügt.
 | 
			
		||||
        es gibt eine neue Meldung. Folgende Nachricht wurde zur Meldung hinzugefügt:
 | 
			
		||||
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
@@ -17,7 +17,7 @@
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
 | 
			
		||||
        Bitte bearbeite die Meldung möglichst bald
 | 
			
		||||
        Bitte bearbeite die Meldung möglichst bald.
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        <a href="{{ report_url }}" class="cta-button">{% translate 'Report bearbeiten' %}</a>
 | 
			
		||||
@@ -10,7 +10,7 @@ from django.core import mail
 | 
			
		||||
from django.utils.html import strip_tags
 | 
			
		||||
 | 
			
		||||
from fellchensammlung.models import AdoptionNotice, Location, RescueOrganization, AdoptionNoticeStatus, Log, \
 | 
			
		||||
    AdoptionNoticeNotification
 | 
			
		||||
    Notification, NotificationTypeChoices
 | 
			
		||||
from fellchensammlung.tools.misc import is_404
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -92,10 +92,11 @@ def deactivate_404_adoption_notices():
 | 
			
		||||
 | 
			
		||||
                deactivation_message = f'Die Vermittlung  [{adoption_notice.name}]({adoption_notice.get_absolute_url()}) wurde automatisch deaktiviert, da die Website unter "Mehr Informationen" nicht mehr online ist.'
 | 
			
		||||
                for subscription in adoption_notice.get_subscriptions():
 | 
			
		||||
                    AdoptionNoticeNotification.objects.create(user=subscription.owner,
 | 
			
		||||
                                                              title="Vermittlung deaktiviert",
 | 
			
		||||
                                                              adoption_notice=adoption_notice,
 | 
			
		||||
                                                              text=deactivation_message)
 | 
			
		||||
                    Notification.objects.create(user_to_notify=subscription.owner,
 | 
			
		||||
                                                notification_type=NotificationTypeChoices.AN_WAS_DEACTIVATED,
 | 
			
		||||
                                                title="Vermittlung deaktiviert",
 | 
			
		||||
                                                adoption_notice=adoption_notice,
 | 
			
		||||
                                                text=deactivation_message)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def dedup_location(location: Location, destructive=False):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
from fellchensammlung.models import User, AdoptionNoticeNotification, TrustLevel
 | 
			
		||||
from fellchensammlung.models import User, Notification, TrustLevel, NotificationTypeChoices
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def notify_of_AN_to_be_checked(adoption_notice):
 | 
			
		||||
@@ -6,8 +6,9 @@ def notify_of_AN_to_be_checked(adoption_notice):
 | 
			
		||||
        users_to_notify = set(User.objects.filter(trust_level__gt=TrustLevel.MODERATOR))
 | 
			
		||||
        users_to_notify.add(adoption_notice.owner)
 | 
			
		||||
        for user in users_to_notify:
 | 
			
		||||
            AdoptionNoticeNotification.objects.create(adoption_notice=adoption_notice,
 | 
			
		||||
                                                      user=user,
 | 
			
		||||
                                                      title=f" Prüfe Vermittlung {adoption_notice}",
 | 
			
		||||
                                                      text=f"{adoption_notice} muss geprüft werden bevor sie veröffentlicht wird.",
 | 
			
		||||
                                                      )
 | 
			
		||||
            Notification.objects.create(adoption_notice=adoption_notice,
 | 
			
		||||
                                        user_to_notify=user,
 | 
			
		||||
                                        notification_type=NotificationTypeChoices.AN_IS_TO_BE_CHECKED,
 | 
			
		||||
                                        title=f" Prüfe Vermittlung {adoption_notice}",
 | 
			
		||||
                                        text=f"{adoption_notice} muss geprüft werden bevor sie veröffentlicht wird.",
 | 
			
		||||
                                        )
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,8 @@ from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from .geo import LocationProxy, Position
 | 
			
		||||
from ..forms import AdoptionNoticeSearchForm
 | 
			
		||||
from ..models import SearchSubscription, AdoptionNotice, AdoptionNoticeNotification, SexChoicesWithAll, Location
 | 
			
		||||
from ..models import SearchSubscription, AdoptionNotice, SexChoicesWithAll, Location, \
 | 
			
		||||
    Notification, NotificationTypeChoices
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def notify_search_subscribers(adoption_notice: AdoptionNotice, only_if_active: bool = True):
 | 
			
		||||
@@ -20,10 +21,11 @@ def notify_search_subscribers(adoption_notice: AdoptionNotice, only_if_active: b
 | 
			
		||||
        search = Search(search_subscription=search_subscription)
 | 
			
		||||
        if search.adoption_notice_fits_search(adoption_notice):
 | 
			
		||||
            notification_text = f"{_('Zu deiner Suche')} {search_subscription} wurde eine neue Vermittlung gefunden"
 | 
			
		||||
            AdoptionNoticeNotification.objects.create(user=search_subscription.owner,
 | 
			
		||||
                                                      title=f"{_('Neue Vermittlung')}: {adoption_notice}",
 | 
			
		||||
                                                      adoption_notice=adoption_notice,
 | 
			
		||||
                                                      text=notification_text)
 | 
			
		||||
            Notification.objects.create(user_to_notify=search_subscription.owner,
 | 
			
		||||
                                        notification_type=NotificationTypeChoices.AN_FOR_SEARCH_FOUND,
 | 
			
		||||
                                        title=f"{_('Neue Vermittlung')}: {adoption_notice}",
 | 
			
		||||
                                        adoption_notice=adoption_notice,
 | 
			
		||||
                                        text=notification_text)
 | 
			
		||||
            logging.debug(f"Notification for search subscription {search_subscription} was sent.")
 | 
			
		||||
        else:
 | 
			
		||||
            logging.debug(f"Adoption notice {adoption_notice} was not fitting the search subscription.")
 | 
			
		||||
 
 | 
			
		||||
@@ -20,9 +20,9 @@ from notfellchen import settings
 | 
			
		||||
 | 
			
		||||
from fellchensammlung import logger
 | 
			
		||||
from .models import AdoptionNotice, Text, Animal, Rule, Image, Report, ModerationAction, \
 | 
			
		||||
    User, Location, AdoptionNoticeStatus, Subscriptions, CommentNotification, BaseNotification, RescueOrganization, \
 | 
			
		||||
    Species, Log, Timestamp, TrustLevel, SexChoicesWithAll, SearchSubscription, AdoptionNoticeNotification, \
 | 
			
		||||
    ImportantLocation, SpeciesSpecificURL
 | 
			
		||||
    User, Location, AdoptionNoticeStatus, Subscriptions, Notification, RescueOrganization, \
 | 
			
		||||
    Species, Log, Timestamp, TrustLevel, SexChoicesWithAll, SearchSubscription, \
 | 
			
		||||
    ImportantLocation, SpeciesSpecificURL, NotificationTypeChoices
 | 
			
		||||
from .forms import AdoptionNoticeForm, ImageForm, ReportAdoptionNoticeForm, \
 | 
			
		||||
    CommentForm, ReportCommentForm, AnimalForm, AdoptionNoticeFormAutoAnimal, SpeciesURLForm, RescueOrgInternalComment
 | 
			
		||||
from .models import Language, Announcement
 | 
			
		||||
@@ -121,10 +121,12 @@ def adoption_notice_detail(request, adoption_notice_id):
 | 
			
		||||
                    for subscription in adoption_notice.get_subscriptions():
 | 
			
		||||
                        # Create a notification but only if the user is not the one that posted the comment
 | 
			
		||||
                        if subscription.owner != request.user:
 | 
			
		||||
                            notification = CommentNotification(user=subscription.owner,
 | 
			
		||||
                                                               title=f"{adoption_notice.name} - Neuer Kommentar",
 | 
			
		||||
                                                               text=f"{request.user}: {comment_instance.text}",
 | 
			
		||||
                                                               comment=comment_instance)
 | 
			
		||||
                            notification = Notification(user_to_notify=subscription.owner,
 | 
			
		||||
                                                        adoption_notice=adoption_notice,
 | 
			
		||||
                                                        notification_type=NotificationTypeChoices.NEW_COMMENT,
 | 
			
		||||
                                                        title=f"{adoption_notice.name} - Neuer Kommentar",
 | 
			
		||||
                                                        text=f"{request.user}: {comment_instance.text}",
 | 
			
		||||
                                                        comment=comment_instance)
 | 
			
		||||
                            notification.save()
 | 
			
		||||
            else:
 | 
			
		||||
                comment_form = CommentForm(instance=adoption_notice)
 | 
			
		||||
@@ -177,7 +179,8 @@ def search_important_locations(request, important_location_slug):
 | 
			
		||||
    search.search_from_predefined_i_location(i_location)
 | 
			
		||||
 | 
			
		||||
    site_title = _("Ratten in %(location_name)s") % {"location_name": i_location.name}
 | 
			
		||||
    site_description = _("Ratten in Tierheimen und Rattenhilfen in der Nähe von %(location_name)s suchen.") % {"location_name": i_location.name}
 | 
			
		||||
    site_description = _("Ratten in Tierheimen und Rattenhilfen in der Nähe von %(location_name)s suchen.") % {
 | 
			
		||||
        "location_name": i_location.name}
 | 
			
		||||
    canonical_url = reverse("search-by-location", args=[i_location.slug])
 | 
			
		||||
 | 
			
		||||
    context = {"adoption_notices": search.get_adoption_notices(),
 | 
			
		||||
@@ -528,7 +531,7 @@ def report_detail_success(request, report_id):
 | 
			
		||||
def user_detail(request, user, token=None):
 | 
			
		||||
    context = {"user": user,
 | 
			
		||||
               "adoption_notices": AdoptionNotice.objects.filter(owner=user),
 | 
			
		||||
               "notifications": BaseNotification.objects.filter(user=user, read=False),
 | 
			
		||||
               "notifications": Notification.objects.filter(user_to_notify=user, read=False),
 | 
			
		||||
               "search_subscriptions": SearchSubscription.objects.filter(owner=user), }
 | 
			
		||||
    if token is not None:
 | 
			
		||||
        context["token"] = token
 | 
			
		||||
@@ -561,13 +564,11 @@ def my_profile(request):
 | 
			
		||||
        action = request.POST.get("action")
 | 
			
		||||
        if action == "notification_mark_read":
 | 
			
		||||
            notification_id = request.POST.get("notification_id")
 | 
			
		||||
            try:
 | 
			
		||||
                notification = CommentNotification.objects.get(pk=notification_id)
 | 
			
		||||
            except CommentNotification.DoesNotExist:
 | 
			
		||||
                notification = BaseNotification.objects.get(pk=notification_id)
 | 
			
		||||
 | 
			
		||||
            notification = Notification.objects.get(pk=notification_id)
 | 
			
		||||
            notification.mark_read()
 | 
			
		||||
        elif action == "notification_mark_all_read":
 | 
			
		||||
            notifications = CommentNotification.objects.filter(user=request.user, mark_read=False)
 | 
			
		||||
            notifications = Notification.objects.filter(user=request.user, mark_read=False)
 | 
			
		||||
            for notification in notifications:
 | 
			
		||||
                notification.mark_read()
 | 
			
		||||
        elif action == "search_subscription_delete":
 | 
			
		||||
@@ -770,11 +771,13 @@ def rescue_organization_check(request, context=None):
 | 
			
		||||
    }
 | 
			
		||||
    rescue_orgs_last_checked = RescueOrganization.objects.filter().order_by("-last_checked")[:10]
 | 
			
		||||
    timeframe = timezone.now().date() - timedelta(days=14)
 | 
			
		||||
    num_rescue_orgs_to_check = RescueOrganization.objects.filter(exclude_from_check=False).filter(last_checked__lt=timeframe).count()
 | 
			
		||||
    num_rescue_orgs_checked = RescueOrganization.objects.filter(exclude_from_check=False).filter(last_checked__gte=timeframe).count()
 | 
			
		||||
    num_rescue_orgs_to_check = RescueOrganization.objects.filter(exclude_from_check=False).filter(
 | 
			
		||||
        last_checked__lt=timeframe).count()
 | 
			
		||||
    num_rescue_orgs_checked = RescueOrganization.objects.filter(exclude_from_check=False).filter(
 | 
			
		||||
        last_checked__gte=timeframe).count()
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        percentage_checked = 100*num_rescue_orgs_checked/(num_rescue_orgs_to_check+num_rescue_orgs_checked)
 | 
			
		||||
        percentage_checked = 100 * num_rescue_orgs_checked / (num_rescue_orgs_to_check + num_rescue_orgs_checked)
 | 
			
		||||
    except ZeroDivisionError:
 | 
			
		||||
        percentage_checked = 100
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -145,6 +145,10 @@ MEDIA_URL = config.get("urls", "media", fallback="/media/")
 | 
			
		||||
# Take all three into account when modifying
 | 
			
		||||
host = config.get("notfellchen", "host", fallback='*')
 | 
			
		||||
 | 
			
		||||
# The base URL will be used to build URLS
 | 
			
		||||
# See https://forum.djangoproject.com/t/putting-full-url-link-on-email-how-to-get-current-domain-name-to-put-on-url/13806/3
 | 
			
		||||
base_url = config.get("notfellchen", "base_url", fallback=f"https://{host}")
 | 
			
		||||
 | 
			
		||||
# see https://docs.djangoproject.com/en/3.2/ref/settings/#std-setting-ALLOWED_HOSTS
 | 
			
		||||
ALLOWED_HOSTS = [host]
 | 
			
		||||
CSRF_TRUSTED_ORIGINS = [f"https://{host}"]
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ from django.utils import timezone
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
from model_bakery import baker
 | 
			
		||||
 | 
			
		||||
from fellchensammlung.models import Announcement, Language, User, TrustLevel, BaseNotification
 | 
			
		||||
from fellchensammlung.models import Announcement, Language, User, TrustLevel, Notification
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserTest(TestCase):
 | 
			
		||||
@@ -85,9 +85,9 @@ class TestNotifications(TestCase):
 | 
			
		||||
        cls.test_user_1 = User.objects.create(username="Testuser1", password="SUPERSECRET", email="test@example.org")
 | 
			
		||||
 | 
			
		||||
    def test_mark_read(self):
 | 
			
		||||
        not1 = BaseNotification.objects.create(user=self.test_user_1, text="New rats to adopt", title="🔔 New Rat alert")
 | 
			
		||||
        not2 = BaseNotification.objects.create(user=self.test_user_1,
 | 
			
		||||
                                               text="New wombat to adopt", title="🔔 New Wombat alert")
 | 
			
		||||
        not1 = Notification.objects.create(user=self.test_user_1, text="New rats to adopt", title="🔔 New Rat alert")
 | 
			
		||||
        not2 = Notification.objects.create(user=self.test_user_1,
 | 
			
		||||
                                           text="New wombat to adopt", title="🔔 New Wombat alert")
 | 
			
		||||
        not1.mark_read()
 | 
			
		||||
 | 
			
		||||
        self.assertTrue(not1.read)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user