Repository: zestedesavoir/zds-site

Issue:
Alerte persistante lorsqu'un billet est dépublié
- publiez un billet
- envoyez un messag
- signalez le message avec un autre utilisateur
- dépubliez le billet

l'alerte est toujours présente.


Discussion:

Resolving patch:
diff --git a/zds/tutorialv2/factories.py b/zds/tutorialv2/factories.py
--- a/zds/tutorialv2/factories.py
+++ b/zds/tutorialv2/factories.py
@@ -136,10 +136,8 @@ def _prepare(cls, create, **kwargs):
         note = super(ContentReactionFactory, cls)._prepare(create, **kwargs)
         note.pubdate = datetime.now()
         note.save()
-        content = kwargs.pop('tutorial', None)
-        if content:
-            content.last_note = note
-            content.save()
+        note.related_content.last_note = note
+        note.related_content.save()
         return note
 
 
diff --git a/zds/tutorialv2/receivers.py b/zds/tutorialv2/receivers.py
--- a/zds/tutorialv2/receivers.py
+++ b/zds/tutorialv2/receivers.py
@@ -5,7 +5,7 @@
 from django.utils.translation import ugettext_lazy as _
 from django.db import models
 
-from zds.tutorialv2.models.database import PublishableContent
+from zds.tutorialv2.models.database import PublishableContent, ContentReaction
 from zds.tutorialv2.signals import content_unpublished
 from zds.gallery.models import Gallery
 from zds.utils import get_current_user
@@ -28,6 +28,12 @@ def cleanup_validation_alerts(sender, instance, *, moderator=None, **__):
                                                                        resolve_reason=_('Le billet a été dépublié.'),
                                                                        solved_date=datetime.datetime.now(),
                                                                        solved=True)
+        reactions = ContentReaction.objects.filter(related_content=instance).values_list('pk', flat=True)
+        Alert.objects.filter(comment__in=reactions).update(moderator=moderator,
+                                                           resolve_reason=_('Le billet a'
+                                                                            ' été dépublié.'),
+                                                           solved_date=datetime.datetime.now(),
+                                                           solved=True)
 
 
 @receiver(models.signals.post_delete, sender=Gallery)
diff --git a/zds/utils/management/commands/clean_alerts_and_notifications.py b/zds/utils/management/commands/clean_alerts_and_notifications.py
new file mode 100644
--- /dev/null
+++ b/zds/utils/management/commands/clean_alerts_and_notifications.py
@@ -0,0 +1,38 @@
+import datetime
+import logging
+
+from django.contrib.auth.models import User
+from django.core.management import BaseCommand
+from django.db import transaction
+from django.conf import settings
+from django.utils.translation import gettext as _
+from zds.utils.models import Alert
+
+
+@transaction.atomic
+class Command(BaseCommand):
+
+    help = 'Clean up useless notifications & alerts.'
+
+    def add_arguments(self, parser):
+        parser.add_argument('--moderator', type=str, dest='moderator_name',
+                            default=settings.ZDS_APP['member']['anonymous_account'])
+
+    def handle(self, *args, **options):
+        moderator = User.objects.filter(username=options['moderator_name']).first()
+        if not moderator:
+            logging.error('Could not use this moderator %s', options['moderator_name'])
+            return 1
+        Alert.objects.first(content__isnull=False, content__public_version__isnull=True).update(
+            moderator=moderator,
+            solved=True,
+            solved_date=datetime.datetime.now(),
+            resolve_reason=_('Résolution automatique.')
+        )
+        Alert.objects.filter(scope='CONTENT', comment__related_content__isnull=False,
+                             comment__related_content__public_version__isnull=True)\
+            .update(
+                moderator=moderator,
+                solved=True,
+                solved_date=datetime.datetime.now(),
+                resolve_reason=_('Résolution automatique.'))


Test patch:
diff --git a/zds/tutorialv2/api/tests.py b/zds/tutorialv2/api/tests.py
--- a/zds/tutorialv2/api/tests.py
+++ b/zds/tutorialv2/api/tests.py
@@ -1,3 +1,4 @@
+import datetime
 from copy import deepcopy
 import os
 import shutil
@@ -34,7 +35,8 @@ def setUp(self):
 
     def test_failure_reaction_karma_with_client_unauthenticated(self):
         author = ProfileFactory()
-        reaction = ContentReactionFactory(author=author.user, position=1, related_content=self.content)
+        reaction = ContentReactionFactory(author=author.user, position=1, related_content=self.content,
+                                          pubdate=datetime.datetime.now())
 
         response = self.client.put(reverse('api:content:reaction-karma', args=(reaction.pk,)))
         self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
diff --git a/zds/tutorialv2/tests/tests_utils.py b/zds/tutorialv2/tests/tests_utils.py
--- a/zds/tutorialv2/tests/tests_utils.py
+++ b/zds/tutorialv2/tests/tests_utils.py
@@ -10,7 +10,7 @@
 
 from zds.member.factories import ProfileFactory, StaffProfileFactory
 from zds.tutorialv2.factories import PublishableContentFactory, ContainerFactory, LicenceFactory, ExtractFactory, \
-    PublishedContentFactory
+    PublishedContentFactory, ContentReactionFactory
 from zds.gallery.factories import UserGalleryFactory
 from zds.tutorialv2.models.versioned import Container
 from zds.tutorialv2.utils import get_target_tagged_tree_for_container, \
@@ -26,6 +26,8 @@
 from mock import Mock
 from copy import deepcopy
 from zds import json_handler
+from zds.utils.models import Alert
+from zds.utils.templatetags.interventions import alerts_list
 
 BASE_DIR = settings.BASE_DIR
 
@@ -582,6 +584,18 @@ def test_image_with_non_ascii_chars(self):
         publish_content(article, article.load_version())
         self.assertTrue(PublishedContent.objects.filter(content_id=article.pk).exists())
 
+    def test_no_alert_on_unpublish(self):
+        """related to #4860"""
+        published = PublishedContentFactory(type='OPINION', author_list=[self.user_author])
+        reaction = ContentReactionFactory(related_content=published, author=ProfileFactory().user, position=1,
+                                          pubdate=datetime.datetime.now())
+        Alert.objects.create(scope='CONTENT', comment=reaction, text='a text', author=ProfileFactory().user,
+                             pubdate=datetime.datetime.now(), content=published)
+        staff = StaffProfileFactory().user
+        self.assertEqual(1, alerts_list(staff)['nb_alerts'])
+        unpublish_content(published, staff)
+        self.assertEqual(0, alerts_list(staff)['nb_alerts'])
+
     def tearDown(self):
         super().tearDown()
         PublicatorRegistry.registry = self.old_registry