Repository: zestedesavoir/zds-site

Issue:
Notification permanante
Bonjour,

Depuis le forum, 

> Je me suis désabonné à un sujet (ne plus suivre) alors que j'avais une notification de se sujet.
> 
> Désormais j'ai en permanence 1 notification. Même si je me réabonne, elle ne disparaît plus ^^"
> Ce bug se combine également avec celui-ci [[v18] Date incorrecte dans les notifications](https://github.com/zestedesavoir/zds-site/issues/3628) .
> 
> D'ailleurs on peut voir ce bug comme l'opposé de celui-ci [Suivre un sujet, messages oubliés](https://github.com/zestedesavoir/zds-site/issues/3654) ».
> 
> Source: [ache](https://zestedesavoir.com/forums/sujet/6300/notification-permanante/)



Discussion:
Je rajoute que quand :
- Je créé un sujet avec l'utilisateur "User"
- J'ajoute un message avec l'utilisateur "Admin"
- Je clique sur le bouton " Ne plus suivre ce sujet" en tant qu'admin, l'api des notifications me renvoi une erreur 500.

C'est peut-être un bug différent ou le même, je sais pas, à investiguer.

Stacktrace:

```
Internal Server Error: /forums/sujet/editer/
Traceback (most recent call last):
  File "/home/hugo/dev/python/zdsenv/lib/python2.7/site-packages/django/core/handlers/base.py", line 132, in get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/hugo/dev/python/zdsenv/lib/python2.7/site-packages/django/views/generic/base.py", line 71, in view
    return self.dispatch(request, *args, **kwargs)
  File "/home/hugo/dev/python/zdsenv/lib/python2.7/site-packages/django/utils/decorators.py", line 34, in _wrapper
    return bound_func(*args, **kwargs)
  File "/home/hugo/dev/python/zdsenv/lib/python2.7/site-packages/django/contrib/auth/decorators.py", line 22, in _wrapped_view
    return view_func(request, *args, **kwargs)
  File "/home/hugo/dev/python/zdsenv/lib/python2.7/site-packages/django/utils/decorators.py", line 30, in bound_func
    return func.__get__(self, type(self))(*args2, **kwargs2)
  File "/home/hugo/dev/python/zdsenv/lib/python2.7/site-packages/django/utils/decorators.py", line 34, in _wrapper
    return bound_func(*args, **kwargs)
  File "/home/hugo/dev/python/zds-site/zds/member/decorator.py", line 27, in _can_write_and_read_now
    return func(request, *args, **kwargs)
  File "/home/hugo/dev/python/zdsenv/lib/python2.7/site-packages/django/utils/decorators.py", line 30, in bound_func
    return func.__get__(self, type(self))(*args2, **kwargs2)
  File "/home/hugo/dev/python/zdsenv/lib/python2.7/site-packages/django/utils/decorators.py", line 34, in _wrapper
    return bound_func(*args, **kwargs)
  File "/home/hugo/dev/python/zdsenv/lib/python2.7/site-packages/django/utils/decorators.py", line 145, in inner
    return func(*args, **kwargs)
  File "/home/hugo/dev/python/zdsenv/lib/python2.7/site-packages/django/utils/decorators.py", line 30, in bound_func
    return func.__get__(self, type(self))(*args2, **kwargs2)
  File "/home/hugo/dev/python/zds-site/zds/forum/views.py", line 266, in dispatch
    return super(TopicEdit, self).dispatch(request, *args, **kwargs)
  File "/home/hugo/dev/python/zdsenv/lib/python2.7/site-packages/django/views/generic/base.py", line 89, in dispatch
    return handler(request, *args, **kwargs)
  File "/home/hugo/dev/python/zds-site/zds/forum/views.py", line 323, in post
    return HttpResponse(json.dumps(response), content_type='application/json')
  File "/usr/lib64/python2.7/json/__init__.py", line 244, in dumps
    return _default_encoder.encode(obj)
  File "/usr/lib64/python2.7/json/encoder.py", line 207, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/lib64/python2.7/json/encoder.py", line 270, in iterencode
    return _iterencode(o, 0)
  File "/usr/lib64/python2.7/json/encoder.py", line 184, in default
    raise TypeError(repr(o) + " is not JSON serializable")
TypeError: <TopicAnswerSubscription: <Abonnement du membre "admin" aux réponses au sujet #6>> is not JSON serializable
```

et la variable response vaut `<Abonnement du membre "admin" aux réponses au sujet #6>`.

De quelle API parles-tu ?

J'ai vu json + requêtte HTTP + 500, j'ai supposé qu'il avait une api mais c'est peut-être pas le cas.

Je confirme le bug de ton premier message par contre je n'ai aucune erreur 500 pour ton second message.

Es-tu certain d'être sur une bonne branche avec toutes les migrations qui vont bien et des données cohérentes ?


Resolving patch:
diff --git a/zds/notification/managers.py b/zds/notification/managers.py
--- a/zds/notification/managers.py
+++ b/zds/notification/managers.py
@@ -4,6 +4,7 @@
 from django.db import models
 
 from zds.forum.models import Topic
+from zds.notification import signals
 from zds.utils import get_current_user
 
 
@@ -136,6 +137,7 @@ def toggle_follow(self, content_object, user=None, by_email=False):
             if by_email:
                 subscription.activate_email()
             return subscription
+        signals.content_read.send(sender=content_object.__class__, instance=content_object, user=user)
         if by_email:
             existing.deactivate_email()
         else:


Test patch:
diff --git a/zds/notification/tests.py b/zds/notification/tests.py
--- a/zds/notification/tests.py
+++ b/zds/notification/tests.py
@@ -517,3 +517,22 @@ def test_reuse_old_notification(self):
         notifications = Notification.objects.filter(subscription__user=self.user1)
         self.assertEqual(1, len(notifications))
         self.assertIsNotNone(notifications.first())
+
+    def test_mark_all_notifications_as_read_when_toggle_follow(self):
+        """
+        When a user unsubscribe to a content, we mark as read all notifications about this content.
+        """
+        category = CategoryFactory(position=1)
+        forum = ForumFactory(category=category, position_in_category=1)
+        topic = TopicFactory(forum=forum, author=self.user1)
+        PostFactory(topic=topic, author=self.user1, position=1)
+        PostFactory(topic=topic, author=self.user2, position=2)
+
+        notifications = Notification.objects.get_unread_notifications_of(self.user1)
+        self.assertEqual(1, len(notifications))
+        self.assertIsNotNone(notifications.first())
+        self.assertEqual(topic.last_message, notifications.first().content_object)
+
+        TopicAnswerSubscription.objects.toggle_follow(topic, self.user1)
+
+        self.assertEqual(0, len(Notification.objects.get_unread_notifications_of(self.user1)))