Repository: zestedesavoir/zds-site

Issue:
Mauvaise gestion de la désinscription dans le journal des événements
**Description du bug**

Sur la branche de dév, quand on a des actions du journal d'événements qui ont été faites par un utilisateur qui s'est désinscrit, la page ne marche plus avec une erreur comme quoi l'URL d'un profil ne peut pas être `reverse`. Ça le fait probablement aussi avec d'autres champs (tout ceux relatifs à des utilisateurs probablement).

**Comment reproduire ?**

* Se connecter avec un utilisateur.
* Créer un billet et le publier.
* Se désinscrire.
* Se reconnecter en staff et aller voir le journal d'événements : la page ne s'affiche pas et on a une erreur "NoReverseMatch".

**Comportement attendu**

Il faudrait que ça fonctionne sans erreur malgré la désinscription. La solution à adopter est probablement la même que pour tous les cas de figure similaires : lors de la désinscription, mettre à jour les champs des événements relatifs à l'utilisateur qui se désinscrit vers `anonymous`.



Discussion:

Resolving patch:
diff --git a/zds/member/views/register.py b/zds/member/views/register.py
--- a/zds/member/views/register.py
+++ b/zds/member/views/register.py
@@ -36,6 +36,7 @@
 from zds.member.views import get_client_ip
 from zds.mp.models import PrivatePost, PrivateTopic
 from zds.tutorialv2.models.database import PickListOperation
+from zds.tutorialv2.models.events import Event
 from zds.utils.models import (
     Comment,
     CommentVote,
@@ -172,6 +173,11 @@ def unregister(request):
     # Nota : as of v21 all about content paternity is held by a proper receiver in zds.tutorialv2.models.database
     PickListOperation.objects.filter(staff_user=current).update(staff_user=anonymous)
     PickListOperation.objects.filter(canceler_user=current).update(canceler_user=anonymous)
+
+    Event.objects.filter(performer=current).update(performer=external)
+    Event.objects.filter(author=current).update(author=external)
+    Event.objects.filter(contributor=current).update(contributor=external)
+
     # Comments likes / dislikes
     votes = CommentVote.objects.filter(user=current)
     for vote in votes:


Test patch:
diff --git a/zds/tutorialv2/tests/factories.py b/zds/tutorialv2/tests/factories.py
--- a/zds/tutorialv2/tests/factories.py
+++ b/zds/tutorialv2/tests/factories.py
@@ -12,7 +12,7 @@
 from zds.utils import old_slugify
 from zds.utils.tests.factories import LicenceFactory, SubCategoryFactory
 from zds.utils.models import Licence
-from zds.tutorialv2.models.database import PublishableContent, Validation, ContentReaction
+from zds.tutorialv2.models.database import PublishableContent, Validation, ContentReaction, ContentContributionRole
 from zds.tutorialv2.models.versioned import Container, Extract
 from zds.tutorialv2.publication_utils import publish_content
 from zds.tutorialv2.utils import init_new_repo
@@ -312,3 +312,12 @@ class Meta:
     description = factory.Sequence("Très belle description n°{}".format)
     position = factory.Sequence(lambda n: n)
     slug = factory.Sequence("mon-objectif-{}".format)
+
+
+class ContentContributionRoleFactory(factory.django.DjangoModelFactory):
+    """Factory that create a role in contributions to contents, for use in tests."""
+
+    class Meta:
+        model = ContentContributionRole
+
+    title = factory.Sequence("Rôle {}".format)
diff --git a/zds/tutorialv2/tests/tests_views/tests_addcontributor.py b/zds/tutorialv2/tests/tests_views/tests_addcontributor.py
--- a/zds/tutorialv2/tests/tests_views/tests_addcontributor.py
+++ b/zds/tutorialv2/tests/tests_views/tests_addcontributor.py
@@ -8,9 +8,9 @@
 
 
 from zds.member.tests.factories import ProfileFactory, StaffProfileFactory
-from zds.tutorialv2.tests.factories import PublishableContentFactory
+from zds.tutorialv2.tests.factories import ContentContributionRoleFactory, PublishableContentFactory
 from zds.tutorialv2.forms import ContributionForm
-from zds.tutorialv2.models.database import ContentContribution, ContentContributionRole
+from zds.tutorialv2.models.database import ContentContribution
 from zds.tutorialv2.tests import TutorialTestMixin, override_for_contents
 
 
@@ -20,12 +20,6 @@ def create_contribution(role, contributor, content):
     return contribution
 
 
-def create_role(title):
-    role = ContentContributionRole(title=title)
-    role.save()
-    return role
-
-
 @override_for_contents()
 class AddContributorPermissionTests(TutorialTestMixin, TestCase):
     """Test permissions and associated behaviors, such as redirections and status codes."""
@@ -40,7 +34,7 @@ def setUp(self):
 
         # Create content
         self.content = PublishableContentFactory(author_list=[self.author])
-        self.role = create_role("Contributeur espiègle")
+        self.role = ContentContributionRoleFactory(title="Contributeur espiègle")
 
         # Get information to be reused in tests
         self.form_url = reverse("content:add-contributor", kwargs={"pk": self.content.pk})
@@ -96,7 +90,7 @@ def setUp(self):
         # Create entities for the test
         self.author = ProfileFactory().user
         self.contributor = ProfileFactory().user
-        self.role = create_role("Validateur")
+        self.role = ContentContributionRoleFactory(title="Validateur")
         self.content = PublishableContentFactory(author_list=[self.author])
         settings.ZDS_APP["member"]["bot_account"] = ProfileFactory().user.username
 
diff --git a/zds/tutorialv2/tests/tests_views/tests_events.py b/zds/tutorialv2/tests/tests_views/tests_events.py
--- a/zds/tutorialv2/tests/tests_views/tests_events.py
+++ b/zds/tutorialv2/tests/tests_views/tests_events.py
@@ -1,10 +1,17 @@
+from django.conf import settings
 from django.test import TestCase
-
 from django.urls import reverse
 
-from zds.member.tests.factories import ProfileFactory, StaffProfileFactory
-from zds.tutorialv2.tests.factories import PublishableContentFactory
+from zds.gallery.tests.factories import UserGalleryFactory
+from zds.member.tests.factories import ProfileFactory, StaffProfileFactory, UserFactory
+from zds.tutorialv2.models.database import PublishedContent
+from zds.tutorialv2.tests.factories import (
+    ContentContributionRoleFactory,
+    PublishableContentFactory,
+    PublishedContentFactory,
+)
 from zds.tutorialv2.tests import override_for_contents, TutorialTestMixin
+from zds.utils.tests.factories import LicenceFactory, SubCategoryFactory
 
 
 @override_for_contents()
@@ -46,4 +53,50 @@ def test_authenticated_outsider(self):
         """Test that unauthorized users get a 403."""
         self.client.force_login(self.outsider)
         response = self.client.get(self.events_list_url)
-        self.assertEquals(response.status_code, 403)
+        self.assertEqual(response.status_code, 403)
+
+
+@override_for_contents()
+class EventListTests(TutorialTestMixin, TestCase):
+    def setUp(self):
+        # Create users
+        self.author = ProfileFactory().user
+        self.coauthor = ProfileFactory().user
+        self.contributor = ProfileFactory().user
+        self.staff = StaffProfileFactory().user
+        self.anonymous = UserFactory(username=settings.ZDS_APP["member"]["anonymous_account"], password="anything")
+        self.external = UserFactory(username=settings.ZDS_APP["member"]["external_account"], password="anything")
+        self.bot = UserFactory(username=settings.ZDS_APP["member"]["bot_account"], password="anything")
+        self.role = ContentContributionRoleFactory()
+
+    def test_events_involving_unregistered_users(self):
+        """Test accessing the event list with actions involving unregistered users."""
+        article = PublishedContentFactory(type="ARTICLE", author_list=[self.author])
+
+        self.client.force_login(self.author)
+
+        # Add a coauthor
+        result = self.client.post(
+            reverse("content:add-author", args=[article.pk]), {"username": self.coauthor.username}, follow=False
+        )
+        self.assertEqual(result.status_code, 302)
+
+        # Add a contributor
+        form_data = {
+            "username": self.contributor,
+            "contribution_role": self.role.pk,
+            "comment": "Foo",
+        }
+        result = self.client.post(reverse("content:add-contributor", args=[article.pk]), form_data, follow=True)
+        self.assertEqual(result.status_code, 200)
+
+        # Unregister users
+        for user in [self.author, self.coauthor, self.contributor]:
+            self.client.force_login(user)
+            response = self.client.post(reverse("member-unregister"), follow=False)
+            self.assertEqual(response.status_code, 302)
+
+        # Access the event list page
+        self.client.force_login(self.staff)
+        response = self.client.get(reverse("content:events", args=[article.pk]))
+        self.assertEqual(response.status_code, 200)
diff --git a/zds/tutorialv2/tests/tests_views/tests_removecontributor.py b/zds/tutorialv2/tests/tests_views/tests_removecontributor.py
--- a/zds/tutorialv2/tests/tests_views/tests_removecontributor.py
+++ b/zds/tutorialv2/tests/tests_views/tests_removecontributor.py
@@ -6,8 +6,8 @@
 from django.utils.html import escape
 
 from zds.member.tests.factories import ProfileFactory, StaffProfileFactory
-from zds.tutorialv2.tests.factories import PublishableContentFactory
-from zds.tutorialv2.models.database import ContentContribution, ContentContributionRole
+from zds.tutorialv2.tests.factories import ContentContributionRoleFactory, PublishableContentFactory
+from zds.tutorialv2.models.database import ContentContribution
 from zds.tutorialv2.tests import TutorialTestMixin, override_for_contents
 
 
@@ -17,12 +17,6 @@ def create_contribution(role, contributor, content):
     return contribution
 
 
-def create_role(title):
-    role = ContentContributionRole(title=title)
-    role.save()
-    return role
-
-
 @override_for_contents()
 class RemoveContributorPermissionTests(TutorialTestMixin, TestCase):
     """Test permissions and associated behaviors, such as redirections and status codes."""
@@ -35,7 +29,7 @@ def setUp(self):
         self.contributor = ProfileFactory().user
 
         # Create a contribution role
-        self.role = create_role("Validateur")
+        self.role = ContentContributionRoleFactory(title="Contributeur espiègle")
 
         # Create content
         self.content = PublishableContentFactory(author_list=[self.author])
@@ -95,8 +89,8 @@ def setUp(self):
         # Create entities for the test
         self.author = ProfileFactory().user
         self.contributor = ProfileFactory().user
-        self.role = create_role("Validateur")
         self.content = PublishableContentFactory(author_list=[self.author])
+        self.role = ContentContributionRoleFactory(title="Validateur")
         self.contribution = create_contribution(self.role, self.contributor, self.content)
 
         # Get information to be reused in tests