We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN 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`.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `zds/member/views/register.py`
Content:
```
1 from datetime import datetime, timedelta
2 
3 from oauth2_provider.models import AccessToken
4 
5 from django.conf import settings
6 from django.contrib import messages
7 from django.contrib.auth import logout
8 from django.contrib.auth.decorators import login_required
9 from django.contrib.auth.models import User
10 from django.core.mail import EmailMultiAlternatives
11 from django.urls import reverse
12 from django.db import transaction
13 from django.db.models import Q
14 from django.shortcuts import redirect, render, get_object_or_404
15 from django.template.loader import render_to_string
16 from django.utils.translation import gettext_lazy as _
17 from django.views.decorators.http import require_POST
18 from django.views.generic import CreateView, FormView
19 
20 from zds.forum.models import Topic
21 from zds.gallery.models import UserGallery
22 from zds.member import NEW_ACCOUNT
23 from zds.member.commons import (
24     ProfileCreate,
25     TokenGenerator,
26 )
27 from zds.member.forms import RegisterForm, UsernameAndEmailForm, LoginForm
28 from zds.member.models import (
29     Profile,
30     TokenRegister,
31     KarmaNote,
32     Ban,
33     BannedEmailProvider,
34     NewEmailProvider,
35 )
36 from zds.member.views import get_client_ip
37 from zds.mp.models import PrivatePost, PrivateTopic
38 from zds.tutorialv2.models.database import PickListOperation
39 from zds.utils.models import (
40     Comment,
41     CommentVote,
42     Alert,
43     CommentEdit,
44     HatRequest,
45     get_hat_from_settings,
46 )
47 import logging
48 
49 from zds.mp.utils import send_mp
50 
51 
52 class RegisterView(CreateView, ProfileCreate, TokenGenerator):
53     """Create a profile."""
54 
55     form_class = RegisterForm
56     template_name = "member/register/index.html"
57 
58     def dispatch(self, *args, **kwargs):
59         return super().dispatch(*args, **kwargs)
60 
61     def get_object(self, queryset=None):
62         return get_object_or_404(Profile, user=self.request.user)
63 
64     def get_form(self, form_class=RegisterForm):
65         return form_class()
66 
67     def post(self, request, *args, **kwargs):
68         form = self.form_class(request.POST)
69 
70         if form.is_valid():
71             return self.form_valid(form)
72         return render(request, self.template_name, {"form": form})
73 
74     def form_valid(self, form):
75         profile = self.create_profile(form.data)
76         profile.last_ip_address = get_client_ip(self.request)
77         profile.username_skeleton = Profile.find_username_skeleton(profile.user.username)
78         self.save_profile(profile)
79         token = self.generate_token(profile.user)
80         try:
81             self.send_email(token, profile.user)
82         except Exception as e:
83             logging.getLogger(__name__).warning("Mail not sent", exc_info=e)
84             messages.warning(self.request, _("Impossible d'envoyer l'email."))
85             self.object = None
86             return self.form_invalid(form)
87         return render(self.request, self.get_success_template())
88 
89     def get_success_template(self):
90         return "member/register/success.html"
91 
92 
93 class SendValidationEmailView(FormView, TokenGenerator):
94     """Send a validation email on demand."""
95 
96     form_class = UsernameAndEmailForm
97     template_name = "member/register/send_validation_email.html"
98 
99     usr = None
100 
101     def get_user(self, username, email):
102 
103         if username:
104             self.usr = get_object_or_404(User, username=username)
105 
106         elif email:
107             self.usr = get_object_or_404(User, email=email)
108 
109     def get_form(self, form_class=UsernameAndEmailForm):
110         return form_class()
111 
112     def post(self, request, *args, **kwargs):
113         form = self.form_class(request.POST)
114 
115         if form.is_valid():
116             # Fetch the user
117             self.get_user(form.data["username"], form.data["email"])
118 
119             # User should not already be active
120             if not self.usr.is_active:
121                 return self.form_valid(form)
122             else:
123                 if form.data["username"]:
124                     form.errors["username"] = form.error_class([self.get_error_message()])
125                 else:
126                     form.errors["email"] = form.error_class([self.get_error_message()])
127 
128         return render(request, self.template_name, {"form": form})
129 
130     def form_valid(self, form):
131         # Delete old token
132         token = TokenRegister.objects.filter(user=self.usr)
133         if token.count() >= 1:
134             token.all().delete()
135 
136         # Generate new token and send email
137         token = self.generate_token(self.usr)
138         try:
139             self.send_email(token, self.usr)
140         except Exception as e:
141             logging.getLogger(__name__).warning("Mail not sent", exc_info=e)
142             messages.warning(_("Impossible d'envoyer l'email."))
143             return self.form_invalid(form)
144 
145         return render(self.request, self.get_success_template())
146 
147     def get_success_template(self):
148         return "member/register/send_validation_email_success.html"
149 
150     def get_error_message(self):
151         return _("Le compte est déjà activé.")
152 
153 
154 @login_required
155 def warning_unregister(request):
156     """
157     Display a warning page showing what will happen when the user
158     unregisters.
159     """
160     return render(request, "member/settings/unregister.html", {"user": request.user})
161 
162 
163 @login_required
164 @require_POST
165 @transaction.atomic
166 def unregister(request):
167     """Allow members to unregister."""
168 
169     anonymous = get_object_or_404(User, username=settings.ZDS_APP["member"]["anonymous_account"])
170     external = get_object_or_404(User, username=settings.ZDS_APP["member"]["external_account"])
171     current = request.user
172     # Nota : as of v21 all about content paternity is held by a proper receiver in zds.tutorialv2.models.database
173     PickListOperation.objects.filter(staff_user=current).update(staff_user=anonymous)
174     PickListOperation.objects.filter(canceler_user=current).update(canceler_user=anonymous)
175     # Comments likes / dislikes
176     votes = CommentVote.objects.filter(user=current)
177     for vote in votes:
178         if vote.positive:
179             vote.comment.like -= 1
180         else:
181             vote.comment.dislike -= 1
182         vote.comment.save()
183     votes.delete()
184     # All contents anonymization
185     Comment.objects.filter(author=current).update(author=anonymous)
186     PrivatePost.objects.filter(author=current).update(author=anonymous)
187     CommentEdit.objects.filter(editor=current).update(editor=anonymous)
188     CommentEdit.objects.filter(deleted_by=current).update(deleted_by=anonymous)
189     # Karma notes, alerts and sanctions anonymization (to keep them)
190     KarmaNote.objects.filter(moderator=current).update(moderator=anonymous)
191     Ban.objects.filter(moderator=current).update(moderator=anonymous)
192     Alert.objects.filter(author=current).update(author=anonymous)
193     Alert.objects.filter(moderator=current).update(moderator=anonymous)
194     BannedEmailProvider.objects.filter(moderator=current).update(moderator=anonymous)
195     # Solved hat requests anonymization
196     HatRequest.objects.filter(moderator=current).update(moderator=anonymous)
197     # In case current user has been moderator in the past
198     Comment.objects.filter(editor=current).update(editor=anonymous)
199     for topic in PrivateTopic.objects.filter(Q(author=current) | Q(participants__in=[current])):
200         if topic.one_participant_remaining():
201             topic.delete()
202         else:
203             topic.remove_participant(current)
204             topic.save()
205     Topic.objects.filter(solved_by=current).update(solved_by=anonymous)
206     Topic.objects.filter(author=current).update(author=anonymous)
207 
208     # Any content exclusively owned by the unregistering member will
209     # be deleted just before the User object (using a pre_delete
210     # receiver).
211     #
212     # Regarding galleries, there are two cases:
213     #
214     # - "personal galleries" with one owner (the unregistering
215     #   user). The user's ownership is removed and replaced by an
216     #   anonymous user in order not to lost the gallery.
217     #
218     # - "personal galleries" with many other owners. It is safe to
219     #   remove the user's ownership, the gallery won't be lost.
220 
221     galleries = UserGallery.objects.filter(user=current)
222     for gallery in galleries:
223         if gallery.gallery.get_linked_users().count() == 1:
224             anonymous_gallery = UserGallery()
225             anonymous_gallery.user = external
226             anonymous_gallery.mode = "w"
227             anonymous_gallery.gallery = gallery.gallery
228             anonymous_gallery.save()
229     galleries.delete()
230 
231     # Remove API access (tokens + applications)
232     for token in AccessToken.objects.filter(user=current):
233         token.revoke()
234 
235     logout(request)
236     User.objects.filter(pk=current.pk).delete()
237     return redirect(reverse("homepage"))
238 
239 
240 def activate_account(request):
241     """Activate an account with a token."""
242     try:
243         token = request.GET["token"]
244     except KeyError:
245         return redirect(reverse("homepage"))
246     token = get_object_or_404(TokenRegister, token=token)
247     usr = token.user
248 
249     # User can't confirm their request if their account is already active
250     if usr.is_active:
251         return render(request, "member/register/token_already_used.html")
252 
253     # User can't confirm their request if it is too late
254     if datetime.now() > token.date_end:
255         return render(request, "member/register/token_failed.html", {"token": token})
256     usr.is_active = True
257     usr.save()
258 
259     # Send welcome message
260     bot = get_object_or_404(User, username=settings.ZDS_APP["member"]["bot_account"])
261     msg = render_to_string(
262         "member/messages/account_activated.md",
263         {
264             "username": usr.username,
265             "site_name": settings.ZDS_APP["site"]["literal_name"],
266             "library_url": settings.ZDS_APP["site"]["url"] + reverse("publication:list"),
267             "opinions_url": settings.ZDS_APP["site"]["url"] + reverse("opinion:list"),
268             "forums_url": settings.ZDS_APP["site"]["url"] + reverse("forum:cats-forums-list"),
269         },
270     )
271 
272     send_mp(
273         bot,
274         [usr],
275         _("Bienvenue sur {}").format(settings.ZDS_APP["site"]["literal_name"]),
276         _("Le manuel du nouveau membre"),
277         msg,
278         send_by_mail=False,
279         leave=True,
280         direct=False,
281         hat=get_hat_from_settings("moderation"),
282     )
283     token.delete()
284 
285     # Create an alert for the staff if it's a new provider
286     if usr.email:
287         provider = usr.email.split("@")[-1].lower()
288         if (
289             not NewEmailProvider.objects.filter(provider=provider).exists()
290             and not User.objects.filter(email__iendswith=f"@{provider}").exclude(pk=usr.pk).exists()
291         ):
292             NewEmailProvider.objects.create(user=usr, provider=provider, use=NEW_ACCOUNT)
293 
294     form = LoginForm(initial={"username": usr.username})
295     return render(request, "member/register/token_success.html", {"usr": usr, "form": form})
296 
297 
298 def generate_token_account(request):
299     """Generate a token for an account."""
300 
301     try:
302         token = request.GET["token"]
303     except KeyError:
304         return redirect(reverse("homepage"))
305     token = get_object_or_404(TokenRegister, token=token)
306 
307     # Push date
308 
309     date_end = datetime.now() + timedelta(days=0, hours=1, minutes=0, seconds=0)
310     token.date_end = date_end
311     token.save()
312 
313     # Send email
314     subject = _("{} - Confirmation d'inscription").format(settings.ZDS_APP["site"]["literal_name"])
315     from_email = "{} <{}>".format(settings.ZDS_APP["site"]["literal_name"], settings.ZDS_APP["site"]["email_noreply"])
316     context = {
317         "username": token.user.username,
318         "site_url": settings.ZDS_APP["site"]["url"],
319         "site_name": settings.ZDS_APP["site"]["literal_name"],
320         "url": settings.ZDS_APP["site"]["url"] + token.get_absolute_url(),
321     }
322     message_html = render_to_string("email/member/confirm_registration.html", context)
323     message_txt = render_to_string("email/member/confirm_registration.txt", context)
324 
325     msg = EmailMultiAlternatives(subject, message_txt, from_email, [token.user.email])
326     msg.attach_alternative(message_html, "text/html")
327     try:
328         msg.send()
329     except:
330         msg = None
331     return render(request, "member/register/success.html", {})
332
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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

{"golden_diff": "diff --git a/zds/member/views/register.py b/zds/member/views/register.py\n--- a/zds/member/views/register.py\n+++ b/zds/member/views/register.py\n@@ -36,6 +36,7 @@\n from zds.member.views import get_client_ip\n from zds.mp.models import PrivatePost, PrivateTopic\n from zds.tutorialv2.models.database import PickListOperation\n+from zds.tutorialv2.models.events import Event\n from zds.utils.models import (\n     Comment,\n     CommentVote,\n@@ -172,6 +173,11 @@\n     # Nota : as of v21 all about content paternity is held by a proper receiver in zds.tutorialv2.models.database\n     PickListOperation.objects.filter(staff_user=current).update(staff_user=anonymous)\n     PickListOperation.objects.filter(canceler_user=current).update(canceler_user=anonymous)\n+\n+    Event.objects.filter(performer=current).update(performer=external)\n+    Event.objects.filter(author=current).update(author=external)\n+    Event.objects.filter(contributor=current).update(contributor=external)\n+\n     # Comments likes / dislikes\n     votes = CommentVote.objects.filter(user=current)\n     for vote in votes:\n", "issue": "Mauvaise gestion de la d\u00e9sinscription dans le journal des \u00e9v\u00e9nements\n**Description du bug**\r\n\r\nSur la branche de d\u00e9v, quand on a des actions du journal d'\u00e9v\u00e9nements qui ont \u00e9t\u00e9 faites par un utilisateur qui s'est d\u00e9sinscrit, la page ne marche plus avec une erreur comme quoi l'URL d'un profil ne peut pas \u00eatre `reverse`. \u00c7a le fait probablement aussi avec d'autres champs (tout ceux relatifs \u00e0 des utilisateurs probablement).\r\n\r\n**Comment reproduire ?**\r\n\r\n* Se connecter avec un utilisateur.\r\n* Cr\u00e9er un billet et le publier.\r\n* Se d\u00e9sinscrire.\r\n* Se reconnecter en staff et aller voir le journal d'\u00e9v\u00e9nements : la page ne s'affiche pas et on a une erreur \"NoReverseMatch\".\r\n\r\n**Comportement attendu**\r\n\r\nIl faudrait que \u00e7a fonctionne sans erreur malgr\u00e9 la d\u00e9sinscription. La solution \u00e0 adopter est probablement la m\u00eame que pour tous les cas de figure similaires : lors de la d\u00e9sinscription, mettre \u00e0 jour les champs des \u00e9v\u00e9nements relatifs \u00e0 l'utilisateur qui se d\u00e9sinscrit vers `anonymous`.\r\n\n", "before_files": [{"content": "from datetime import datetime, timedelta\n\nfrom oauth2_provider.models import AccessToken\n\nfrom django.conf import settings\nfrom django.contrib import messages\nfrom django.contrib.auth import logout\nfrom django.contrib.auth.decorators import login_required\nfrom django.contrib.auth.models import User\nfrom django.core.mail import EmailMultiAlternatives\nfrom django.urls import reverse\nfrom django.db import transaction\nfrom django.db.models import Q\nfrom django.shortcuts import redirect, render, get_object_or_404\nfrom django.template.loader import render_to_string\nfrom django.utils.translation import gettext_lazy as _\nfrom django.views.decorators.http import require_POST\nfrom django.views.generic import CreateView, FormView\n\nfrom zds.forum.models import Topic\nfrom zds.gallery.models import UserGallery\nfrom zds.member import NEW_ACCOUNT\nfrom zds.member.commons import (\n    ProfileCreate,\n    TokenGenerator,\n)\nfrom zds.member.forms import RegisterForm, UsernameAndEmailForm, LoginForm\nfrom zds.member.models import (\n    Profile,\n    TokenRegister,\n    KarmaNote,\n    Ban,\n    BannedEmailProvider,\n    NewEmailProvider,\n)\nfrom zds.member.views import get_client_ip\nfrom zds.mp.models import PrivatePost, PrivateTopic\nfrom zds.tutorialv2.models.database import PickListOperation\nfrom zds.utils.models import (\n    Comment,\n    CommentVote,\n    Alert,\n    CommentEdit,\n    HatRequest,\n    get_hat_from_settings,\n)\nimport logging\n\nfrom zds.mp.utils import send_mp\n\n\nclass RegisterView(CreateView, ProfileCreate, TokenGenerator):\n    \"\"\"Create a profile.\"\"\"\n\n    form_class = RegisterForm\n    template_name = \"member/register/index.html\"\n\n    def dispatch(self, *args, **kwargs):\n        return super().dispatch(*args, **kwargs)\n\n    def get_object(self, queryset=None):\n        return get_object_or_404(Profile, user=self.request.user)\n\n    def get_form(self, form_class=RegisterForm):\n        return form_class()\n\n    def post(self, request, *args, **kwargs):\n        form = self.form_class(request.POST)\n\n        if form.is_valid():\n            return self.form_valid(form)\n        return render(request, self.template_name, {\"form\": form})\n\n    def form_valid(self, form):\n        profile = self.create_profile(form.data)\n        profile.last_ip_address = get_client_ip(self.request)\n        profile.username_skeleton = Profile.find_username_skeleton(profile.user.username)\n        self.save_profile(profile)\n        token = self.generate_token(profile.user)\n        try:\n            self.send_email(token, profile.user)\n        except Exception as e:\n            logging.getLogger(__name__).warning(\"Mail not sent\", exc_info=e)\n            messages.warning(self.request, _(\"Impossible d'envoyer l'email.\"))\n            self.object = None\n            return self.form_invalid(form)\n        return render(self.request, self.get_success_template())\n\n    def get_success_template(self):\n        return \"member/register/success.html\"\n\n\nclass SendValidationEmailView(FormView, TokenGenerator):\n    \"\"\"Send a validation email on demand.\"\"\"\n\n    form_class = UsernameAndEmailForm\n    template_name = \"member/register/send_validation_email.html\"\n\n    usr = None\n\n    def get_user(self, username, email):\n\n        if username:\n            self.usr = get_object_or_404(User, username=username)\n\n        elif email:\n            self.usr = get_object_or_404(User, email=email)\n\n    def get_form(self, form_class=UsernameAndEmailForm):\n        return form_class()\n\n    def post(self, request, *args, **kwargs):\n        form = self.form_class(request.POST)\n\n        if form.is_valid():\n            # Fetch the user\n            self.get_user(form.data[\"username\"], form.data[\"email\"])\n\n            # User should not already be active\n            if not self.usr.is_active:\n                return self.form_valid(form)\n            else:\n                if form.data[\"username\"]:\n                    form.errors[\"username\"] = form.error_class([self.get_error_message()])\n                else:\n                    form.errors[\"email\"] = form.error_class([self.get_error_message()])\n\n        return render(request, self.template_name, {\"form\": form})\n\n    def form_valid(self, form):\n        # Delete old token\n        token = TokenRegister.objects.filter(user=self.usr)\n        if token.count() >= 1:\n            token.all().delete()\n\n        # Generate new token and send email\n        token = self.generate_token(self.usr)\n        try:\n            self.send_email(token, self.usr)\n        except Exception as e:\n            logging.getLogger(__name__).warning(\"Mail not sent\", exc_info=e)\n            messages.warning(_(\"Impossible d'envoyer l'email.\"))\n            return self.form_invalid(form)\n\n        return render(self.request, self.get_success_template())\n\n    def get_success_template(self):\n        return \"member/register/send_validation_email_success.html\"\n\n    def get_error_message(self):\n        return _(\"Le compte est d\u00e9j\u00e0 activ\u00e9.\")\n\n\n@login_required\ndef warning_unregister(request):\n    \"\"\"\n    Display a warning page showing what will happen when the user\n    unregisters.\n    \"\"\"\n    return render(request, \"member/settings/unregister.html\", {\"user\": request.user})\n\n\n@login_required\n@require_POST\n@transaction.atomic\ndef unregister(request):\n    \"\"\"Allow members to unregister.\"\"\"\n\n    anonymous = get_object_or_404(User, username=settings.ZDS_APP[\"member\"][\"anonymous_account\"])\n    external = get_object_or_404(User, username=settings.ZDS_APP[\"member\"][\"external_account\"])\n    current = request.user\n    # Nota : as of v21 all about content paternity is held by a proper receiver in zds.tutorialv2.models.database\n    PickListOperation.objects.filter(staff_user=current).update(staff_user=anonymous)\n    PickListOperation.objects.filter(canceler_user=current).update(canceler_user=anonymous)\n    # Comments likes / dislikes\n    votes = CommentVote.objects.filter(user=current)\n    for vote in votes:\n        if vote.positive:\n            vote.comment.like -= 1\n        else:\n            vote.comment.dislike -= 1\n        vote.comment.save()\n    votes.delete()\n    # All contents anonymization\n    Comment.objects.filter(author=current).update(author=anonymous)\n    PrivatePost.objects.filter(author=current).update(author=anonymous)\n    CommentEdit.objects.filter(editor=current).update(editor=anonymous)\n    CommentEdit.objects.filter(deleted_by=current).update(deleted_by=anonymous)\n    # Karma notes, alerts and sanctions anonymization (to keep them)\n    KarmaNote.objects.filter(moderator=current).update(moderator=anonymous)\n    Ban.objects.filter(moderator=current).update(moderator=anonymous)\n    Alert.objects.filter(author=current).update(author=anonymous)\n    Alert.objects.filter(moderator=current).update(moderator=anonymous)\n    BannedEmailProvider.objects.filter(moderator=current).update(moderator=anonymous)\n    # Solved hat requests anonymization\n    HatRequest.objects.filter(moderator=current).update(moderator=anonymous)\n    # In case current user has been moderator in the past\n    Comment.objects.filter(editor=current).update(editor=anonymous)\n    for topic in PrivateTopic.objects.filter(Q(author=current) | Q(participants__in=[current])):\n        if topic.one_participant_remaining():\n            topic.delete()\n        else:\n            topic.remove_participant(current)\n            topic.save()\n    Topic.objects.filter(solved_by=current).update(solved_by=anonymous)\n    Topic.objects.filter(author=current).update(author=anonymous)\n\n    # Any content exclusively owned by the unregistering member will\n    # be deleted just before the User object (using a pre_delete\n    # receiver).\n    #\n    # Regarding galleries, there are two cases:\n    #\n    # - \"personal galleries\" with one owner (the unregistering\n    #   user). The user's ownership is removed and replaced by an\n    #   anonymous user in order not to lost the gallery.\n    #\n    # - \"personal galleries\" with many other owners. It is safe to\n    #   remove the user's ownership, the gallery won't be lost.\n\n    galleries = UserGallery.objects.filter(user=current)\n    for gallery in galleries:\n        if gallery.gallery.get_linked_users().count() == 1:\n            anonymous_gallery = UserGallery()\n            anonymous_gallery.user = external\n            anonymous_gallery.mode = \"w\"\n            anonymous_gallery.gallery = gallery.gallery\n            anonymous_gallery.save()\n    galleries.delete()\n\n    # Remove API access (tokens + applications)\n    for token in AccessToken.objects.filter(user=current):\n        token.revoke()\n\n    logout(request)\n    User.objects.filter(pk=current.pk).delete()\n    return redirect(reverse(\"homepage\"))\n\n\ndef activate_account(request):\n    \"\"\"Activate an account with a token.\"\"\"\n    try:\n        token = request.GET[\"token\"]\n    except KeyError:\n        return redirect(reverse(\"homepage\"))\n    token = get_object_or_404(TokenRegister, token=token)\n    usr = token.user\n\n    # User can't confirm their request if their account is already active\n    if usr.is_active:\n        return render(request, \"member/register/token_already_used.html\")\n\n    # User can't confirm their request if it is too late\n    if datetime.now() > token.date_end:\n        return render(request, \"member/register/token_failed.html\", {\"token\": token})\n    usr.is_active = True\n    usr.save()\n\n    # Send welcome message\n    bot = get_object_or_404(User, username=settings.ZDS_APP[\"member\"][\"bot_account\"])\n    msg = render_to_string(\n        \"member/messages/account_activated.md\",\n        {\n            \"username\": usr.username,\n            \"site_name\": settings.ZDS_APP[\"site\"][\"literal_name\"],\n            \"library_url\": settings.ZDS_APP[\"site\"][\"url\"] + reverse(\"publication:list\"),\n            \"opinions_url\": settings.ZDS_APP[\"site\"][\"url\"] + reverse(\"opinion:list\"),\n            \"forums_url\": settings.ZDS_APP[\"site\"][\"url\"] + reverse(\"forum:cats-forums-list\"),\n        },\n    )\n\n    send_mp(\n        bot,\n        [usr],\n        _(\"Bienvenue sur {}\").format(settings.ZDS_APP[\"site\"][\"literal_name\"]),\n        _(\"Le manuel du nouveau membre\"),\n        msg,\n        send_by_mail=False,\n        leave=True,\n        direct=False,\n        hat=get_hat_from_settings(\"moderation\"),\n    )\n    token.delete()\n\n    # Create an alert for the staff if it's a new provider\n    if usr.email:\n        provider = usr.email.split(\"@\")[-1].lower()\n        if (\n            not NewEmailProvider.objects.filter(provider=provider).exists()\n            and not User.objects.filter(email__iendswith=f\"@{provider}\").exclude(pk=usr.pk).exists()\n        ):\n            NewEmailProvider.objects.create(user=usr, provider=provider, use=NEW_ACCOUNT)\n\n    form = LoginForm(initial={\"username\": usr.username})\n    return render(request, \"member/register/token_success.html\", {\"usr\": usr, \"form\": form})\n\n\ndef generate_token_account(request):\n    \"\"\"Generate a token for an account.\"\"\"\n\n    try:\n        token = request.GET[\"token\"]\n    except KeyError:\n        return redirect(reverse(\"homepage\"))\n    token = get_object_or_404(TokenRegister, token=token)\n\n    # Push date\n\n    date_end = datetime.now() + timedelta(days=0, hours=1, minutes=0, seconds=0)\n    token.date_end = date_end\n    token.save()\n\n    # Send email\n    subject = _(\"{} - Confirmation d'inscription\").format(settings.ZDS_APP[\"site\"][\"literal_name\"])\n    from_email = \"{} <{}>\".format(settings.ZDS_APP[\"site\"][\"literal_name\"], settings.ZDS_APP[\"site\"][\"email_noreply\"])\n    context = {\n        \"username\": token.user.username,\n        \"site_url\": settings.ZDS_APP[\"site\"][\"url\"],\n        \"site_name\": settings.ZDS_APP[\"site\"][\"literal_name\"],\n        \"url\": settings.ZDS_APP[\"site\"][\"url\"] + token.get_absolute_url(),\n    }\n    message_html = render_to_string(\"email/member/confirm_registration.html\", context)\n    message_txt = render_to_string(\"email/member/confirm_registration.txt\", context)\n\n    msg = EmailMultiAlternatives(subject, message_txt, from_email, [token.user.email])\n    msg.attach_alternative(message_html, \"text/html\")\n    try:\n        msg.send()\n    except:\n        msg = None\n    return render(request, \"member/register/success.html\", {})\n", "path": "zds/member/views/register.py"}], "after_files": [{"content": "from datetime import datetime, timedelta\n\nfrom oauth2_provider.models import AccessToken\n\nfrom django.conf import settings\nfrom django.contrib import messages\nfrom django.contrib.auth import logout\nfrom django.contrib.auth.decorators import login_required\nfrom django.contrib.auth.models import User\nfrom django.core.mail import EmailMultiAlternatives\nfrom django.urls import reverse\nfrom django.db import transaction\nfrom django.db.models import Q\nfrom django.shortcuts import redirect, render, get_object_or_404\nfrom django.template.loader import render_to_string\nfrom django.utils.translation import gettext_lazy as _\nfrom django.views.decorators.http import require_POST\nfrom django.views.generic import CreateView, FormView\n\nfrom zds.forum.models import Topic\nfrom zds.gallery.models import UserGallery\nfrom zds.member import NEW_ACCOUNT\nfrom zds.member.commons import (\n    ProfileCreate,\n    TokenGenerator,\n)\nfrom zds.member.forms import RegisterForm, UsernameAndEmailForm, LoginForm\nfrom zds.member.models import (\n    Profile,\n    TokenRegister,\n    KarmaNote,\n    Ban,\n    BannedEmailProvider,\n    NewEmailProvider,\n)\nfrom zds.member.views import get_client_ip\nfrom zds.mp.models import PrivatePost, PrivateTopic\nfrom zds.tutorialv2.models.database import PickListOperation\nfrom zds.tutorialv2.models.events import Event\nfrom zds.utils.models import (\n    Comment,\n    CommentVote,\n    Alert,\n    CommentEdit,\n    HatRequest,\n    get_hat_from_settings,\n)\nimport logging\n\nfrom zds.mp.utils import send_mp\n\n\nclass RegisterView(CreateView, ProfileCreate, TokenGenerator):\n    \"\"\"Create a profile.\"\"\"\n\n    form_class = RegisterForm\n    template_name = \"member/register/index.html\"\n\n    def dispatch(self, *args, **kwargs):\n        return super().dispatch(*args, **kwargs)\n\n    def get_object(self, queryset=None):\n        return get_object_or_404(Profile, user=self.request.user)\n\n    def get_form(self, form_class=RegisterForm):\n        return form_class()\n\n    def post(self, request, *args, **kwargs):\n        form = self.form_class(request.POST)\n\n        if form.is_valid():\n            return self.form_valid(form)\n        return render(request, self.template_name, {\"form\": form})\n\n    def form_valid(self, form):\n        profile = self.create_profile(form.data)\n        profile.last_ip_address = get_client_ip(self.request)\n        profile.username_skeleton = Profile.find_username_skeleton(profile.user.username)\n        self.save_profile(profile)\n        token = self.generate_token(profile.user)\n        try:\n            self.send_email(token, profile.user)\n        except Exception as e:\n            logging.getLogger(__name__).warning(\"Mail not sent\", exc_info=e)\n            messages.warning(self.request, _(\"Impossible d'envoyer l'email.\"))\n            self.object = None\n            return self.form_invalid(form)\n        return render(self.request, self.get_success_template())\n\n    def get_success_template(self):\n        return \"member/register/success.html\"\n\n\nclass SendValidationEmailView(FormView, TokenGenerator):\n    \"\"\"Send a validation email on demand.\"\"\"\n\n    form_class = UsernameAndEmailForm\n    template_name = \"member/register/send_validation_email.html\"\n\n    usr = None\n\n    def get_user(self, username, email):\n\n        if username:\n            self.usr = get_object_or_404(User, username=username)\n\n        elif email:\n            self.usr = get_object_or_404(User, email=email)\n\n    def get_form(self, form_class=UsernameAndEmailForm):\n        return form_class()\n\n    def post(self, request, *args, **kwargs):\n        form = self.form_class(request.POST)\n\n        if form.is_valid():\n            # Fetch the user\n            self.get_user(form.data[\"username\"], form.data[\"email\"])\n\n            # User should not already be active\n            if not self.usr.is_active:\n                return self.form_valid(form)\n            else:\n                if form.data[\"username\"]:\n                    form.errors[\"username\"] = form.error_class([self.get_error_message()])\n                else:\n                    form.errors[\"email\"] = form.error_class([self.get_error_message()])\n\n        return render(request, self.template_name, {\"form\": form})\n\n    def form_valid(self, form):\n        # Delete old token\n        token = TokenRegister.objects.filter(user=self.usr)\n        if token.count() >= 1:\n            token.all().delete()\n\n        # Generate new token and send email\n        token = self.generate_token(self.usr)\n        try:\n            self.send_email(token, self.usr)\n        except Exception as e:\n            logging.getLogger(__name__).warning(\"Mail not sent\", exc_info=e)\n            messages.warning(_(\"Impossible d'envoyer l'email.\"))\n            return self.form_invalid(form)\n\n        return render(self.request, self.get_success_template())\n\n    def get_success_template(self):\n        return \"member/register/send_validation_email_success.html\"\n\n    def get_error_message(self):\n        return _(\"Le compte est d\u00e9j\u00e0 activ\u00e9.\")\n\n\n@login_required\ndef warning_unregister(request):\n    \"\"\"\n    Display a warning page showing what will happen when the user\n    unregisters.\n    \"\"\"\n    return render(request, \"member/settings/unregister.html\", {\"user\": request.user})\n\n\n@login_required\n@require_POST\n@transaction.atomic\ndef unregister(request):\n    \"\"\"Allow members to unregister.\"\"\"\n\n    anonymous = get_object_or_404(User, username=settings.ZDS_APP[\"member\"][\"anonymous_account\"])\n    external = get_object_or_404(User, username=settings.ZDS_APP[\"member\"][\"external_account\"])\n    current = request.user\n    # Nota : as of v21 all about content paternity is held by a proper receiver in zds.tutorialv2.models.database\n    PickListOperation.objects.filter(staff_user=current).update(staff_user=anonymous)\n    PickListOperation.objects.filter(canceler_user=current).update(canceler_user=anonymous)\n\n    Event.objects.filter(performer=current).update(performer=external)\n    Event.objects.filter(author=current).update(author=external)\n    Event.objects.filter(contributor=current).update(contributor=external)\n\n    # Comments likes / dislikes\n    votes = CommentVote.objects.filter(user=current)\n    for vote in votes:\n        if vote.positive:\n            vote.comment.like -= 1\n        else:\n            vote.comment.dislike -= 1\n        vote.comment.save()\n    votes.delete()\n    # All contents anonymization\n    Comment.objects.filter(author=current).update(author=anonymous)\n    PrivatePost.objects.filter(author=current).update(author=anonymous)\n    CommentEdit.objects.filter(editor=current).update(editor=anonymous)\n    CommentEdit.objects.filter(deleted_by=current).update(deleted_by=anonymous)\n    # Karma notes, alerts and sanctions anonymization (to keep them)\n    KarmaNote.objects.filter(moderator=current).update(moderator=anonymous)\n    Ban.objects.filter(moderator=current).update(moderator=anonymous)\n    Alert.objects.filter(author=current).update(author=anonymous)\n    Alert.objects.filter(moderator=current).update(moderator=anonymous)\n    BannedEmailProvider.objects.filter(moderator=current).update(moderator=anonymous)\n    # Solved hat requests anonymization\n    HatRequest.objects.filter(moderator=current).update(moderator=anonymous)\n    # In case current user has been moderator in the past\n    Comment.objects.filter(editor=current).update(editor=anonymous)\n    for topic in PrivateTopic.objects.filter(Q(author=current) | Q(participants__in=[current])):\n        if topic.one_participant_remaining():\n            topic.delete()\n        else:\n            topic.remove_participant(current)\n            topic.save()\n    Topic.objects.filter(solved_by=current).update(solved_by=anonymous)\n    Topic.objects.filter(author=current).update(author=anonymous)\n\n    # Any content exclusively owned by the unregistering member will\n    # be deleted just before the User object (using a pre_delete\n    # receiver).\n    #\n    # Regarding galleries, there are two cases:\n    #\n    # - \"personal galleries\" with one owner (the unregistering\n    #   user). The user's ownership is removed and replaced by an\n    #   anonymous user in order not to lost the gallery.\n    #\n    # - \"personal galleries\" with many other owners. It is safe to\n    #   remove the user's ownership, the gallery won't be lost.\n\n    galleries = UserGallery.objects.filter(user=current)\n    for gallery in galleries:\n        if gallery.gallery.get_linked_users().count() == 1:\n            anonymous_gallery = UserGallery()\n            anonymous_gallery.user = external\n            anonymous_gallery.mode = \"w\"\n            anonymous_gallery.gallery = gallery.gallery\n            anonymous_gallery.save()\n    galleries.delete()\n\n    # Remove API access (tokens + applications)\n    for token in AccessToken.objects.filter(user=current):\n        token.revoke()\n\n    logout(request)\n    User.objects.filter(pk=current.pk).delete()\n    return redirect(reverse(\"homepage\"))\n\n\ndef activate_account(request):\n    \"\"\"Activate an account with a token.\"\"\"\n    try:\n        token = request.GET[\"token\"]\n    except KeyError:\n        return redirect(reverse(\"homepage\"))\n    token = get_object_or_404(TokenRegister, token=token)\n    usr = token.user\n\n    # User can't confirm their request if their account is already active\n    if usr.is_active:\n        return render(request, \"member/register/token_already_used.html\")\n\n    # User can't confirm their request if it is too late\n    if datetime.now() > token.date_end:\n        return render(request, \"member/register/token_failed.html\", {\"token\": token})\n    usr.is_active = True\n    usr.save()\n\n    # Send welcome message\n    bot = get_object_or_404(User, username=settings.ZDS_APP[\"member\"][\"bot_account\"])\n    msg = render_to_string(\n        \"member/messages/account_activated.md\",\n        {\n            \"username\": usr.username,\n            \"site_name\": settings.ZDS_APP[\"site\"][\"literal_name\"],\n            \"library_url\": settings.ZDS_APP[\"site\"][\"url\"] + reverse(\"publication:list\"),\n            \"opinions_url\": settings.ZDS_APP[\"site\"][\"url\"] + reverse(\"opinion:list\"),\n            \"forums_url\": settings.ZDS_APP[\"site\"][\"url\"] + reverse(\"forum:cats-forums-list\"),\n        },\n    )\n\n    send_mp(\n        bot,\n        [usr],\n        _(\"Bienvenue sur {}\").format(settings.ZDS_APP[\"site\"][\"literal_name\"]),\n        _(\"Le manuel du nouveau membre\"),\n        msg,\n        send_by_mail=False,\n        leave=True,\n        direct=False,\n        hat=get_hat_from_settings(\"moderation\"),\n    )\n    token.delete()\n\n    # Create an alert for the staff if it's a new provider\n    if usr.email:\n        provider = usr.email.split(\"@\")[-1].lower()\n        if (\n            not NewEmailProvider.objects.filter(provider=provider).exists()\n            and not User.objects.filter(email__iendswith=f\"@{provider}\").exclude(pk=usr.pk).exists()\n        ):\n            NewEmailProvider.objects.create(user=usr, provider=provider, use=NEW_ACCOUNT)\n\n    form = LoginForm(initial={\"username\": usr.username})\n    return render(request, \"member/register/token_success.html\", {\"usr\": usr, \"form\": form})\n\n\ndef generate_token_account(request):\n    \"\"\"Generate a token for an account.\"\"\"\n\n    try:\n        token = request.GET[\"token\"]\n    except KeyError:\n        return redirect(reverse(\"homepage\"))\n    token = get_object_or_404(TokenRegister, token=token)\n\n    # Push date\n\n    date_end = datetime.now() + timedelta(days=0, hours=1, minutes=0, seconds=0)\n    token.date_end = date_end\n    token.save()\n\n    # Send email\n    subject = _(\"{} - Confirmation d'inscription\").format(settings.ZDS_APP[\"site\"][\"literal_name\"])\n    from_email = \"{} <{}>\".format(settings.ZDS_APP[\"site\"][\"literal_name\"], settings.ZDS_APP[\"site\"][\"email_noreply\"])\n    context = {\n        \"username\": token.user.username,\n        \"site_url\": settings.ZDS_APP[\"site\"][\"url\"],\n        \"site_name\": settings.ZDS_APP[\"site\"][\"literal_name\"],\n        \"url\": settings.ZDS_APP[\"site\"][\"url\"] + token.get_absolute_url(),\n    }\n    message_html = render_to_string(\"email/member/confirm_registration.html\", context)\n    message_txt = render_to_string(\"email/member/confirm_registration.txt\", context)\n\n    msg = EmailMultiAlternatives(subject, message_txt, from_email, [token.user.email])\n    msg.attach_alternative(message_html, \"text/html\")\n    try:\n        msg.send()\n    except:\n        msg = None\n    return render(request, \"member/register/success.html\", {})\n", "path": "zds/member/views/register.py"}]}