Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(notification): passage des notifications en lues #712

Merged
merged 3 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion lacommunaute/forum_conversation/tests/tests_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from lacommunaute.forum_conversation.views import PostDeleteView, TopicCreateView
from lacommunaute.forum_moderation.factories import BlockedDomainNameFactory, BlockedEmailFactory
from lacommunaute.forum_upvote.factories import UpVoteFactory
from lacommunaute.notification.factories import NotificationFactory
from lacommunaute.users.factories import UserFactory
from lacommunaute.utils.testing import parse_response_to_soup

Expand Down Expand Up @@ -713,14 +714,26 @@ def test_delete_link_visibility(self):
status_code=200,
)

def test_get_marks_notifications_read(self):
self.client.force_login(self.poster)

notification = NotificationFactory(recipient=self.poster.email, post=self.topic.first_post)
self.assertIsNone(notification.sent_at)

response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)

notification.refresh_from_db()
self.assertEqual(str(notification.created), str(notification.sent_at))

def test_numqueries(self):
PostFactory.create_batch(10, topic=self.topic, poster=self.poster)
UpVoteFactory(content_object=self.topic.last_post, voter=UserFactory())
CertifiedPostFactory(topic=self.topic, post=self.topic.last_post, user=UserFactory())
self.client.force_login(self.poster)

# note vincentporte : to be optimized
with self.assertNumQueries(39):
with self.assertNumQueries(40):
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)

Expand Down
13 changes: 13 additions & 0 deletions lacommunaute/forum_conversation/tests/tests_views_htmx.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from lacommunaute.forum_moderation.factories import BlockedDomainNameFactory, BlockedEmailFactory
from lacommunaute.forum_moderation.models import BlockedPost
from lacommunaute.forum_upvote.factories import UpVoteFactory
from lacommunaute.notification.factories import NotificationFactory
from lacommunaute.users.factories import UserFactory


Expand Down Expand Up @@ -210,6 +211,18 @@ def test_certified_post_highlight(self):
response = self.client.get(self.url)
self.assertContains(response, "Certifié par la Plateforme de l'Inclusion", status_code=200)

def test_get_marks_notifications_read(self):
self.client.force_login(self.user)

notification = NotificationFactory(recipient=self.user.email, post=self.topic.first_post)
self.assertIsNone(notification.sent_at)

response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)

notification.refresh_from_db()
self.assertEqual(str(notification.created), str(notification.sent_at))


class PostFeedCreateViewTest(TestCase):
@classmethod
Expand Down
6 changes: 6 additions & 0 deletions lacommunaute/forum_conversation/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from lacommunaute.forum_conversation.models import Topic
from lacommunaute.forum_conversation.shortcuts import can_certify_post, get_posts_of_a_topic_except_first_one
from lacommunaute.forum_conversation.view_mixins import FilteredTopicsListViewMixin
from lacommunaute.notification.models import Notification


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -113,6 +114,11 @@ def get_context_data(self, **kwargs):
def get_queryset(self):
return get_posts_of_a_topic_except_first_one(self.topic, self.request.user)

def get(self, request, *args, **kwargs):
if request.user.is_authenticated:
Notification.objects.mark_topic_posts_read(self.get_topic(), request.user)
return super().get(request, *args, **kwargs)


class TopicListView(FilteredTopicsListViewMixin, ListView):
context_object_name = "topics"
Expand Down
3 changes: 3 additions & 0 deletions lacommunaute/forum_conversation/views_htmx.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from lacommunaute.forum_conversation.forms import PostForm
from lacommunaute.forum_conversation.models import CertifiedPost, Post, Topic
from lacommunaute.forum_conversation.shortcuts import can_certify_post, get_posts_of_a_topic_except_first_one
from lacommunaute.notification.models import Notification


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -67,6 +68,8 @@ def get(self, request, **kwargs):
topic = self.get_topic()

track_handler.mark_topic_read(topic, request.user)
if request.user.is_authenticated:
Notification.objects.mark_topic_posts_read(topic, request.user)

return render(
request,
Expand Down
2 changes: 1 addition & 1 deletion lacommunaute/notification/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def queryset(self, request, queryset):

@admin.register(Notification)
class NotificationAdmin(admin.ModelAdmin):
list_display = ("kind", "delay", "created", "sent_at")
list_display = ("recipient", "kind", "delay", "created", "sent_at", "post_id")
list_filter = ("kind", "delay", SentNotificationListFilter)
raw_id_fields = ("post",)
search_fields = ("recipient", "post__id", "post__subject")
6 changes: 5 additions & 1 deletion lacommunaute/notification/factories.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import random
from datetime import timedelta

import factory
import factory.django
from django.utils import timezone
from faker import Faker

from lacommunaute.notification.enums import EmailSentTrackKind
Expand Down Expand Up @@ -28,4 +32,4 @@ class Meta:
model = Notification

class Params:
is_sent = factory.Trait(sent_at=faker.past_datetime())
is_sent = factory.Trait(sent_at=(timezone.now() - timedelta(days=random.randint(0, 90))))
12 changes: 12 additions & 0 deletions lacommunaute/notification/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from operator import attrgetter

from django.db import models
from django.db.models import F
from django.utils.translation import gettext_lazy as _
from machina.models.abstract_models import DatedModel

Expand Down Expand Up @@ -31,6 +32,17 @@ def group_by_recipient(self):
for recipient, group in groupby(self.order_by("recipient", "kind"), key=attrgetter("recipient"))
}

def mark_topic_posts_read(self, topic, user):
"""
Called when a topic's posts are read - to update the read status of associated Notification
"""
if not topic or (not user or user.is_anonymous):
raise ValueError()

self.filter(
sent_at__isnull=True, recipient=user.email, post__in=topic.posts.values_list("id", flat=True)
).update(sent_at=F("created"))


class Notification(DatedModel):
recipient = models.EmailField(verbose_name=_("recipient"), null=False, blank=False)
Expand Down
58 changes: 58 additions & 0 deletions lacommunaute/notification/tests/tests_models.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
from django.contrib.auth.models import AnonymousUser
from django.db.models import F
from django.test import TestCase

from lacommunaute.forum_conversation.factories import TopicFactory
from lacommunaute.notification.enums import EmailSentTrackKind
from lacommunaute.notification.factories import NotificationFactory
from lacommunaute.notification.models import EmailSentTrack, Notification
from lacommunaute.users.factories import UserFactory


class EmailSentTrackModelTest(TestCase):
Expand Down Expand Up @@ -31,3 +35,57 @@ def test_notification_group_by_recipient(self):
)

self.assertEqual(result[recipient_b], [notification_b])

def test_mark_topic_posts_read(self):
user = UserFactory()
topic = TopicFactory(with_post=True)

NotificationFactory.create_batch(2, recipient=user.email, post=topic.first_post)

Notification.objects.mark_topic_posts_read(topic, user)

self.assertEqual(
Notification.objects.filter(post=topic.first_post, recipient=user.email, sent_at=F("created")).count(),
2,
)

def test_mark_topic_posts_read_doesnt_impact_old_notifications(self):
user = UserFactory()
topic = TopicFactory(with_post=True)

old_notification = NotificationFactory(recipient=user.email, post=topic.first_post, is_sent=True)
self.assertIsNotNone(old_notification.sent_at)
self.assertNotEqual(str(old_notification.sent_at), str(old_notification.created))
original_send_time = old_notification.sent_at

Notification.objects.mark_topic_posts_read(topic, user)

old_notification.refresh_from_db()
self.assertEqual(original_send_time, old_notification.sent_at)

def test_mark_topic_posts_read_doesnt_impact_other_notifications(self):
user = UserFactory()
topic = TopicFactory(with_post=True)

other_notification = NotificationFactory(recipient="[email protected]", post=topic.first_post)

Notification.objects.mark_topic_posts_read(topic, user)

other_notification.refresh_from_db()
self.assertIsNone(other_notification.sent_at)

def test_mark_topic_posts_read_anonymous_user(self):
topic = TopicFactory(with_post=True)

with self.assertRaises(ValueError):
Notification.objects.mark_topic_posts_read(topic, AnonymousUser())

def test_mark_topic_posts_read_invalid_arguments(self):
user = UserFactory()
topic = TopicFactory(with_post=True)

with self.assertRaises(ValueError):
Notification.objects.mark_topic_posts_read(None, user)

with self.assertRaises(ValueError):
Notification.objects.mark_topic_posts_read(topic, None)
Loading