feat: Completely rework notification framework

This change is needed as
a) there are many different types of notifications. A notification about a new comment does not only relate the comment but also adoption notice and a user that made the comment. Therefore, I introduce a new general notification that has optional links to other objects.
b) this change allows to define a different representation of e-mails vs. display on the website. The new notification type is in charge of representation from now on. Title and text will be deprecated and eventually removed
This commit is contained in:
2025-07-11 11:39:55 +02:00
parent 648466aa70
commit e99798ba5c
18 changed files with 317 additions and 93 deletions

View File

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

View File

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

View File

@@ -6,7 +6,7 @@ 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 fellchensammlung.models import User, Notification, TrustLevel, NotificationTypeChoices
from notfellchen.settings import host
NEWLINE = "\r\n"
@@ -19,10 +19,10 @@ def mail_admins_new_report(report):
for moderator in User.objects.filter(trust_level__gt=TrustLevel.MODERATOR):
report_url = "https://" + host + 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)
notification = Notification.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()
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"] = 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/report.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)

View File

@@ -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',
),
]

View File

@@ -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(blank=True, help_text='Useraccount der Benachrichtigt wird', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL, verbose_name='Nutzer*in'),
),
]

View File

@@ -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):
@@ -479,7 +479,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 +906,49 @@ 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,
blank=True, null=True,
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 +956,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'))

View File

@@ -1,23 +1,22 @@
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):
print("Dada")
if not created or not instance.user_to_notify.email_notifications:
return
else:
print("Dodo")
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 +39,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()

View File

@@ -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_absolute_url }}" class="cta-button">{% translate 'Vermittlung anzeigen' %}</a>
</p>
{% endblock %}

View File

@@ -0,0 +1,16 @@
{% 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_absolute_url }}" class="cta-button">{% translate 'Vermittlung anzeigen' %}</a>
</p>
{% endblock %}

View File

@@ -0,0 +1,16 @@
{% 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_absolute_url }}" class="cta-button">{% translate 'Vermittlung anzeigen' %}</a>
</p>
{% endblock %}

View File

@@ -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_absolute_url }}" class="cta-button">{% translate 'Vermittlung anzeigen' %}</a>
</p>
{% endblock %}

View File

@@ -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 %}

View File

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

View File

@@ -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):

View File

@@ -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.",
)

View File

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

View File

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

View File

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