prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Possible erreur 500 à la résolution d'une alerte sur un contenu qui n'est plus public
  Rapporté par Sentry. J'ai eu du mal à comprendre comment le bug a pu se produire, mais j'ai réussi à le reproduire (d'une façon peut-être un peu tirée par les cheveux...).
  
  **Comment reproduire ?**
  
  1. Se connecter en tant que `user1`
  2. Signaler un billet
  3. Se connecter en tant que `staff`
  4. Ouvrir la page du billet signalé dans deux onglets différents
  5. Sur un des onglets, dépublier le billet
  6. Sur l'autre onglet, résoudre l'alerte (ne pas recharger la page juste avant, le billet n'est en fait plus publié, c'est là qu'est l'astuce)
  
  Une erreur 500 va alors apparaître. Elle provient d'ici : https://github.com/zestedesavoir/zds-site/blob/c06671c4901a95c30f31067c09d5e4526fd86575/zds/tutorialv2/views/alerts.py#L88
  Le contenu n'a plus de version publique, donc plus d'URL publique, et `content.get_absolute_url_online()` renvoie alors `''`.
  
  La correction de ce bug passe sans doute par la vérification si l'alerte est déjà résolue ou si le contenu signalé a bien une version publique : si l'une de ces conditions n'est pas remplie, une erreur 404 devrait être levée.
  
  
  --- 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/tutorialv2/views/alerts.py`
  Content:
  ```
  1 from datetime import datetime
  2 
  3 from django.contrib import messages
  4 from django.core.exceptions import PermissionDenied
  5 from django.contrib.auth.mixins import LoginRequiredMixin
  6 from django.db import transaction
  7 from django.http import Http404
  8 from django.shortcuts import get_object_or_404, redirect
  9 from django.template.loader import render_to_string
  10 from django.utils.decorators import method_decorator
  11 from django.utils.translation import gettext_lazy as _
  12 from django.views.generic import FormView
  13 
  14 from zds.tutorialv2.models import TYPE_CHOICES_DICT
  15 from zds.tutorialv2.models.database import PublishableContent
  16 from zds.utils.models import Alert
  17 
  18 
  19 class SendContentAlert(LoginRequiredMixin, FormView):
  20     http_method_names = ["post"]
  21 
  22     @method_decorator(transaction.atomic)
  23     def dispatch(self, *args, **kwargs):
  24         return super().dispatch(*args, **kwargs)
  25 
  26     def post(self, request, *args, **kwargs):
  27         try:
  28             content_pk = int(self.kwargs["pk"])
  29         except (KeyError, ValueError):
  30             raise Http404("Identifiant manquant ou conversion en entier impossible.")
  31         content = get_object_or_404(PublishableContent, pk=content_pk)
  32 
  33         if len(request.POST["signal_text"].strip()) == 0:
  34             messages.error(request, _("La raison du signalement ne peut pas être vide."))
  35         else:
  36             alert = Alert(
  37                 author=request.user,
  38                 content=content,
  39                 scope="CONTENT",
  40                 text=request.POST["signal_text"],
  41                 pubdate=datetime.now(),
  42             )
  43             alert.save()
  44 
  45             human_content_type = TYPE_CHOICES_DICT[content.type].lower()
  46             messages.success(self.request, _("Ce {} a bien été signalé aux modérateurs.").format(human_content_type))
  47 
  48         return redirect(content.get_absolute_url_online())
  49 
  50 
  51 class SolveContentAlert(LoginRequiredMixin, FormView):
  52     @method_decorator(transaction.atomic)
  53     def dispatch(self, *args, **kwargs):
  54         return super().dispatch(*args, **kwargs)
  55 
  56     def post(self, request, *args, **kwargs):
  57         if not request.user.has_perm("tutorialv2.change_contentreaction"):
  58             raise PermissionDenied
  59         try:
  60             alert = get_object_or_404(Alert, pk=int(request.POST["alert_pk"]))
  61             content = PublishableContent.objects.get(pk=alert.content.id)
  62         except (KeyError, ValueError):
  63             raise Http404("L'alerte n'existe pas.")
  64 
  65         resolve_reason = ""
  66         msg_title = ""
  67         msg_content = ""
  68         if "text" in request.POST and request.POST["text"]:
  69             resolve_reason = request.POST["text"]
  70             authors = alert.content.authors.values_list("username", flat=True)
  71             authors = ", ".join(authors)
  72             msg_title = _("Résolution d'alerte : {0}").format(content.title)
  73             msg_content = render_to_string(
  74                 "tutorialv2/messages/resolve_alert.md",
  75                 {
  76                     "content": content,
  77                     "url": content.get_absolute_url_online(),
  78                     "name": alert.author.username,
  79                     "target_name": authors,
  80                     "modo_name": request.user.username,
  81                     "message": "\n".join(["> " + line for line in resolve_reason.split("\n")]),
  82                     "alert_text": "\n".join(["> " + line for line in alert.text.split("\n")]),
  83                 },
  84             )
  85         alert.solve(request.user, resolve_reason, msg_title, msg_content)
  86 
  87         messages.success(self.request, _("L'alerte a bien été résolue."))
  88         return redirect(content.get_absolute_url_online())
  89
  ```
  --- 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()
  
  ```
  
golden_diff: diff --git a/zds/tutorialv2/views/alerts.py b/zds/tutorialv2/views/alerts.py
  --- a/zds/tutorialv2/views/alerts.py
  +++ b/zds/tutorialv2/views/alerts.py
  @@ -62,6 +62,9 @@
           except (KeyError, ValueError):
               raise Http404("L'alerte n'existe pas.")
   
  +        if alert.solved:
  +            raise Http404("L'alerte a déjà été résolue.")
  +
           resolve_reason = ""
           msg_title = ""
           msg_content = ""
  
verification_info: {"golden_diff": "diff --git a/zds/tutorialv2/views/alerts.py b/zds/tutorialv2/views/alerts.py\n--- a/zds/tutorialv2/views/alerts.py\n+++ b/zds/tutorialv2/views/alerts.py\n@@ -62,6 +62,9 @@\n         except (KeyError, ValueError):\n             raise Http404(\"L'alerte n'existe pas.\")\n \n+        if alert.solved:\n+            raise Http404(\"L'alerte a d\u00e9j\u00e0 \u00e9t\u00e9 r\u00e9solue.\")\n+\n         resolve_reason = \"\"\n         msg_title = \"\"\n         msg_content = \"\"\n", "issue": "Possible erreur 500 \u00e0 la r\u00e9solution d'une alerte sur un contenu qui n'est plus public\nRapport\u00e9 par Sentry. J'ai eu du mal \u00e0 comprendre comment le bug a pu se produire, mais j'ai r\u00e9ussi \u00e0 le reproduire (d'une fa\u00e7on peut-\u00eatre un peu tir\u00e9e par les cheveux...).\r\n\r\n**Comment reproduire ?**\r\n\r\n1. Se connecter en tant que `user1`\r\n2. Signaler un billet\r\n3. Se connecter en tant que `staff`\r\n4. Ouvrir la page du billet signal\u00e9 dans deux onglets diff\u00e9rents\r\n5. Sur un des onglets, d\u00e9publier le billet\r\n6. Sur l'autre onglet, r\u00e9soudre l'alerte (ne pas recharger la page juste avant, le billet n'est en fait plus publi\u00e9, c'est l\u00e0 qu'est l'astuce)\r\n\r\nUne erreur 500 va alors appara\u00eetre. Elle provient d'ici : https://github.com/zestedesavoir/zds-site/blob/c06671c4901a95c30f31067c09d5e4526fd86575/zds/tutorialv2/views/alerts.py#L88\r\nLe contenu n'a plus de version publique, donc plus d'URL publique, et `content.get_absolute_url_online()` renvoie alors `''`.\r\n\r\nLa correction de ce bug passe sans doute par la v\u00e9rification si l'alerte est d\u00e9j\u00e0 r\u00e9solue ou si le contenu signal\u00e9 a bien une version publique : si l'une de ces conditions n'est pas remplie, une erreur 404 devrait \u00eatre lev\u00e9e.\r\n\n", "before_files": [{"content": "from datetime import datetime\n\nfrom django.contrib import messages\nfrom django.core.exceptions import PermissionDenied\nfrom django.contrib.auth.mixins import LoginRequiredMixin\nfrom django.db import transaction\nfrom django.http import Http404\nfrom django.shortcuts import get_object_or_404, redirect\nfrom django.template.loader import render_to_string\nfrom django.utils.decorators import method_decorator\nfrom django.utils.translation import gettext_lazy as _\nfrom django.views.generic import FormView\n\nfrom zds.tutorialv2.models import TYPE_CHOICES_DICT\nfrom zds.tutorialv2.models.database import PublishableContent\nfrom zds.utils.models import Alert\n\n\nclass SendContentAlert(LoginRequiredMixin, FormView):\n    http_method_names = [\"post\"]\n\n    @method_decorator(transaction.atomic)\n    def dispatch(self, *args, **kwargs):\n        return super().dispatch(*args, **kwargs)\n\n    def post(self, request, *args, **kwargs):\n        try:\n            content_pk = int(self.kwargs[\"pk\"])\n        except (KeyError, ValueError):\n            raise Http404(\"Identifiant manquant ou conversion en entier impossible.\")\n        content = get_object_or_404(PublishableContent, pk=content_pk)\n\n        if len(request.POST[\"signal_text\"].strip()) == 0:\n            messages.error(request, _(\"La raison du signalement ne peut pas \u00eatre vide.\"))\n        else:\n            alert = Alert(\n                author=request.user,\n                content=content,\n                scope=\"CONTENT\",\n                text=request.POST[\"signal_text\"],\n                pubdate=datetime.now(),\n            )\n            alert.save()\n\n            human_content_type = TYPE_CHOICES_DICT[content.type].lower()\n            messages.success(self.request, _(\"Ce {} a bien \u00e9t\u00e9 signal\u00e9 aux mod\u00e9rateurs.\").format(human_content_type))\n\n        return redirect(content.get_absolute_url_online())\n\n\nclass SolveContentAlert(LoginRequiredMixin, FormView):\n    @method_decorator(transaction.atomic)\n    def dispatch(self, *args, **kwargs):\n        return super().dispatch(*args, **kwargs)\n\n    def post(self, request, *args, **kwargs):\n        if not request.user.has_perm(\"tutorialv2.change_contentreaction\"):\n            raise PermissionDenied\n        try:\n            alert = get_object_or_404(Alert, pk=int(request.POST[\"alert_pk\"]))\n            content = PublishableContent.objects.get(pk=alert.content.id)\n        except (KeyError, ValueError):\n            raise Http404(\"L'alerte n'existe pas.\")\n\n        resolve_reason = \"\"\n        msg_title = \"\"\n        msg_content = \"\"\n        if \"text\" in request.POST and request.POST[\"text\"]:\n            resolve_reason = request.POST[\"text\"]\n            authors = alert.content.authors.values_list(\"username\", flat=True)\n            authors = \", \".join(authors)\n            msg_title = _(\"R\u00e9solution d'alerte : {0}\").format(content.title)\n            msg_content = render_to_string(\n                \"tutorialv2/messages/resolve_alert.md\",\n                {\n                    \"content\": content,\n                    \"url\": content.get_absolute_url_online(),\n                    \"name\": alert.author.username,\n                    \"target_name\": authors,\n                    \"modo_name\": request.user.username,\n                    \"message\": \"\\n\".join([\"> \" + line for line in resolve_reason.split(\"\\n\")]),\n                    \"alert_text\": \"\\n\".join([\"> \" + line for line in alert.text.split(\"\\n\")]),\n                },\n            )\n        alert.solve(request.user, resolve_reason, msg_title, msg_content)\n\n        messages.success(self.request, _(\"L'alerte a bien \u00e9t\u00e9 r\u00e9solue.\"))\n        return redirect(content.get_absolute_url_online())\n", "path": "zds/tutorialv2/views/alerts.py"}], "after_files": [{"content": "from datetime import datetime\n\nfrom django.contrib import messages\nfrom django.core.exceptions import PermissionDenied\nfrom django.contrib.auth.mixins import LoginRequiredMixin\nfrom django.db import transaction\nfrom django.http import Http404\nfrom django.shortcuts import get_object_or_404, redirect\nfrom django.template.loader import render_to_string\nfrom django.utils.decorators import method_decorator\nfrom django.utils.translation import gettext_lazy as _\nfrom django.views.generic import FormView\n\nfrom zds.tutorialv2.models import TYPE_CHOICES_DICT\nfrom zds.tutorialv2.models.database import PublishableContent\nfrom zds.utils.models import Alert\n\n\nclass SendContentAlert(LoginRequiredMixin, FormView):\n    http_method_names = [\"post\"]\n\n    @method_decorator(transaction.atomic)\n    def dispatch(self, *args, **kwargs):\n        return super().dispatch(*args, **kwargs)\n\n    def post(self, request, *args, **kwargs):\n        try:\n            content_pk = int(self.kwargs[\"pk\"])\n        except (KeyError, ValueError):\n            raise Http404(\"Identifiant manquant ou conversion en entier impossible.\")\n        content = get_object_or_404(PublishableContent, pk=content_pk)\n\n        if len(request.POST[\"signal_text\"].strip()) == 0:\n            messages.error(request, _(\"La raison du signalement ne peut pas \u00eatre vide.\"))\n        else:\n            alert = Alert(\n                author=request.user,\n                content=content,\n                scope=\"CONTENT\",\n                text=request.POST[\"signal_text\"],\n                pubdate=datetime.now(),\n            )\n            alert.save()\n\n            human_content_type = TYPE_CHOICES_DICT[content.type].lower()\n            messages.success(self.request, _(\"Ce {} a bien \u00e9t\u00e9 signal\u00e9 aux mod\u00e9rateurs.\").format(human_content_type))\n\n        return redirect(content.get_absolute_url_online())\n\n\nclass SolveContentAlert(LoginRequiredMixin, FormView):\n    @method_decorator(transaction.atomic)\n    def dispatch(self, *args, **kwargs):\n        return super().dispatch(*args, **kwargs)\n\n    def post(self, request, *args, **kwargs):\n        if not request.user.has_perm(\"tutorialv2.change_contentreaction\"):\n            raise PermissionDenied\n        try:\n            alert = get_object_or_404(Alert, pk=int(request.POST[\"alert_pk\"]))\n            content = PublishableContent.objects.get(pk=alert.content.id)\n        except (KeyError, ValueError):\n            raise Http404(\"L'alerte n'existe pas.\")\n\n        if alert.solved:\n            raise Http404(\"L'alerte a d\u00e9j\u00e0 \u00e9t\u00e9 r\u00e9solue.\")\n\n        resolve_reason = \"\"\n        msg_title = \"\"\n        msg_content = \"\"\n        if \"text\" in request.POST and request.POST[\"text\"]:\n            resolve_reason = request.POST[\"text\"]\n            authors = alert.content.authors.values_list(\"username\", flat=True)\n            authors = \", \".join(authors)\n            msg_title = _(\"R\u00e9solution d'alerte : {0}\").format(content.title)\n            msg_content = render_to_string(\n                \"tutorialv2/messages/resolve_alert.md\",\n                {\n                    \"content\": content,\n                    \"url\": content.get_absolute_url_online(),\n                    \"name\": alert.author.username,\n                    \"target_name\": authors,\n                    \"modo_name\": request.user.username,\n                    \"message\": \"\\n\".join([\"> \" + line for line in resolve_reason.split(\"\\n\")]),\n                    \"alert_text\": \"\\n\".join([\"> \" + line for line in alert.text.split(\"\\n\")]),\n                },\n            )\n        alert.solve(request.user, resolve_reason, msg_title, msg_content)\n\n        messages.success(self.request, _(\"L'alerte a bien \u00e9t\u00e9 r\u00e9solue.\"))\n        return redirect(content.get_absolute_url_online())\n", "path": "zds/tutorialv2/views/alerts.py"}]}