prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Add an option to link to multiple strings
  *This issue was created automatically by a [script](https://github.com/mathjazz/bugzilla2github/).*
  ## [Bug 1324933](https://bugzilla.mozilla.org/show_bug.cgi?id=1324933)
  Bug Reporter: @tomer
  CC:   @gaby2300, @ItielMaN, @mathjazz
  Blocker for:  [Bug 1390931](https://bugzilla.mozilla.org/show_bug.cgi?id=1390931)
  
  In dxr.mozilla.org I can select multiple lines in a file and than have a link that will show these lines highlighted. I'd suggest having a similar feature in pontoon, so we could send a team member a link that contains multiple strings, instead of one of the following workarounds:
  
  * Sending one string per link
  * Sending link to a search results or a resource, and asking them to look on the second string, the third from the bottom, etc. 
  
  I'd like to have a link similar to this one: 
  https://pontoon.mozilla.org/he/firefox-aurora/all-resources/?string=75295,75296 
  … Which will show me both strings in the list.
  
  
  As for the UI for creating such lists, I guess having control-click to toggle multiple selections will be enough; this is an advanced feature after all. ☺
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `pontoon/projects/management/commands/send_review_notifications.py`
  Content:
  ```
  1 from collections import defaultdict
  2 from datetime import timedelta
  3 from urllib.parse import urlencode
  4 
  5 from django.core.management.base import BaseCommand
  6 from django.db.models import Q
  7 from django.urls import reverse
  8 from django.utils import timezone
  9 from notifications.signals import notify
  10 from pontoon.base.models import Translation
  11 
  12 
  13 class Command(BaseCommand):
  14     help = "Notify translators about their newly reviewed suggestions"
  15 
  16     def get_description(self, author, notifyData):
  17         desc = "Your suggestions have been reviewed:\n<ul>"
  18 
  19         for (locale, project), (approved, rejected) in notifyData.items():
  20             url = reverse(
  21                 "pontoon.translate",
  22                 kwargs={
  23                     "locale": locale.code,
  24                     "project": project.slug,
  25                     "resource": "all-resources",
  26                 },
  27             )
  28             url += "?" + urlencode({"author": author.email})
  29             if len(approved) == 1 and len(rejected) == 0:
  30                 url += "&" + urlencode({"string": approved[0]})
  31             elif len(approved) == 0 and len(rejected) == 1:
  32                 url += "&" + urlencode({"string": rejected[0]})
  33 
  34             # Filter out rejections where the author's own suggestion replaced the previous
  35             rejected = [x for x in rejected if x not in approved]
  36 
  37             if len(approved) == 0:
  38                 msg = f"{len(rejected)} Rejected"
  39             else:
  40                 msg = f"{len(approved)} Approved"
  41                 if len(rejected) > 0:
  42                     msg += f", {len(rejected)} Rejected"
  43 
  44             desc += (
  45                 f'\n<li><a href="{url}">{project.name} ({locale.code})</a>: {msg}</li>'
  46             )
  47 
  48         return desc + "\n</ul>"
  49 
  50     def handle(self, *args, **options):
  51         """
  52         This command sends notifications about newly reviewed
  53         suggestions to the authors of those suggestions.
  54 
  55         The command is designed to run on a daily basis.
  56         """
  57         self.stdout.write("Sending review notifications...")
  58 
  59         # (author) -> (locale, project) -> (approved, rejected)
  60         data = defaultdict(lambda: defaultdict(lambda: (list(), list())))
  61         start = timezone.now() - timedelta(days=1)
  62         for suggestion in Translation.objects.filter(
  63             (Q(approved_date__gt=start) | Q(rejected_date__gt=start))
  64             & Q(user__profile__review_notifications=True)
  65         ):
  66             author = suggestion.user
  67             locale = suggestion.locale
  68             project = suggestion.entity.resource.project
  69 
  70             if suggestion.approved and suggestion.approved_user != author:
  71                 data[author][(locale, project)][0].append(suggestion.entity.pk)
  72             elif suggestion.rejected and suggestion.rejected_user != author:
  73                 data[author][(locale, project)][1].append(suggestion.entity.pk)
  74 
  75         for author, notifyData in data.items():
  76             desc = self.get_description(author, notifyData)
  77             notify.send(
  78                 sender=author,
  79                 recipient=author,
  80                 verb="has reviewed suggestions",
  81                 description=desc,
  82             )
  83 
  84         self.stdout.write(f"Sent {len(data)} review notifications.")
  85
  ```
  --- 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/pontoon/projects/management/commands/send_review_notifications.py b/pontoon/projects/management/commands/send_review_notifications.py
  --- a/pontoon/projects/management/commands/send_review_notifications.py
  +++ b/pontoon/projects/management/commands/send_review_notifications.py
  @@ -1,6 +1,5 @@
   from collections import defaultdict
   from datetime import timedelta
  -from urllib.parse import urlencode
   
   from django.core.management.base import BaseCommand
   from django.db.models import Q
  @@ -13,7 +12,7 @@
   class Command(BaseCommand):
       help = "Notify translators about their newly reviewed suggestions"
   
  -    def get_description(self, author, notifyData):
  +    def get_description(self, notifyData):
           desc = "Your suggestions have been reviewed:\n<ul>"
   
           for (locale, project), (approved, rejected) in notifyData.items():
  @@ -25,11 +24,8 @@
                       "resource": "all-resources",
                   },
               )
  -            url += "?" + urlencode({"author": author.email})
  -            if len(approved) == 1 and len(rejected) == 0:
  -                url += "&" + urlencode({"string": approved[0]})
  -            elif len(approved) == 0 and len(rejected) == 1:
  -                url += "&" + urlencode({"string": rejected[0]})
  +            list = map(str, approved + rejected)
  +            url += "?list=" + ",".join(list)
   
               # Filter out rejections where the author's own suggestion replaced the previous
               rejected = [x for x in rejected if x not in approved]
  @@ -73,7 +69,7 @@
                   data[author][(locale, project)][1].append(suggestion.entity.pk)
   
           for author, notifyData in data.items():
  -            desc = self.get_description(author, notifyData)
  +            desc = self.get_description(notifyData)
               notify.send(
                   sender=author,
                   recipient=author,
  
verification_info: {"golden_diff": "diff --git a/pontoon/projects/management/commands/send_review_notifications.py b/pontoon/projects/management/commands/send_review_notifications.py\n--- a/pontoon/projects/management/commands/send_review_notifications.py\n+++ b/pontoon/projects/management/commands/send_review_notifications.py\n@@ -1,6 +1,5 @@\n from collections import defaultdict\n from datetime import timedelta\n-from urllib.parse import urlencode\n \n from django.core.management.base import BaseCommand\n from django.db.models import Q\n@@ -13,7 +12,7 @@\n class Command(BaseCommand):\n     help = \"Notify translators about their newly reviewed suggestions\"\n \n-    def get_description(self, author, notifyData):\n+    def get_description(self, notifyData):\n         desc = \"Your suggestions have been reviewed:\\n<ul>\"\n \n         for (locale, project), (approved, rejected) in notifyData.items():\n@@ -25,11 +24,8 @@\n                     \"resource\": \"all-resources\",\n                 },\n             )\n-            url += \"?\" + urlencode({\"author\": author.email})\n-            if len(approved) == 1 and len(rejected) == 0:\n-                url += \"&\" + urlencode({\"string\": approved[0]})\n-            elif len(approved) == 0 and len(rejected) == 1:\n-                url += \"&\" + urlencode({\"string\": rejected[0]})\n+            list = map(str, approved + rejected)\n+            url += \"?list=\" + \",\".join(list)\n \n             # Filter out rejections where the author's own suggestion replaced the previous\n             rejected = [x for x in rejected if x not in approved]\n@@ -73,7 +69,7 @@\n                 data[author][(locale, project)][1].append(suggestion.entity.pk)\n \n         for author, notifyData in data.items():\n-            desc = self.get_description(author, notifyData)\n+            desc = self.get_description(notifyData)\n             notify.send(\n                 sender=author,\n                 recipient=author,\n", "issue": "Add an option to link to multiple strings\n*This issue was created automatically by a [script](https://github.com/mathjazz/bugzilla2github/).*\n## [Bug 1324933](https://bugzilla.mozilla.org/show_bug.cgi?id=1324933)\nBug Reporter: @tomer\nCC:   @gaby2300, @ItielMaN, @mathjazz\nBlocker for:  [Bug 1390931](https://bugzilla.mozilla.org/show_bug.cgi?id=1390931)\n\nIn dxr.mozilla.org I can select multiple lines in a file and than have a link that will show these lines highlighted. I'd suggest having a similar feature in pontoon, so we could send a team member a link that contains multiple strings, instead of one of the following workarounds:\n\n* Sending one string per link\n* Sending link to a search results or a resource, and asking them to look on the second string, the third from the bottom, etc. \n\nI'd like to have a link similar to this one: \nhttps://pontoon.mozilla.org/he/firefox-aurora/all-resources/?string=75295,75296 \n\u2026 Which will show me both strings in the list.\n\n\nAs for the UI for creating such lists, I guess having control-click to toggle multiple selections will be enough; this is an advanced feature after all. \u263a\n", "before_files": [{"content": "from collections import defaultdict\nfrom datetime import timedelta\nfrom urllib.parse import urlencode\n\nfrom django.core.management.base import BaseCommand\nfrom django.db.models import Q\nfrom django.urls import reverse\nfrom django.utils import timezone\nfrom notifications.signals import notify\nfrom pontoon.base.models import Translation\n\n\nclass Command(BaseCommand):\n    help = \"Notify translators about their newly reviewed suggestions\"\n\n    def get_description(self, author, notifyData):\n        desc = \"Your suggestions have been reviewed:\\n<ul>\"\n\n        for (locale, project), (approved, rejected) in notifyData.items():\n            url = reverse(\n                \"pontoon.translate\",\n                kwargs={\n                    \"locale\": locale.code,\n                    \"project\": project.slug,\n                    \"resource\": \"all-resources\",\n                },\n            )\n            url += \"?\" + urlencode({\"author\": author.email})\n            if len(approved) == 1 and len(rejected) == 0:\n                url += \"&\" + urlencode({\"string\": approved[0]})\n            elif len(approved) == 0 and len(rejected) == 1:\n                url += \"&\" + urlencode({\"string\": rejected[0]})\n\n            # Filter out rejections where the author's own suggestion replaced the previous\n            rejected = [x for x in rejected if x not in approved]\n\n            if len(approved) == 0:\n                msg = f\"{len(rejected)} Rejected\"\n            else:\n                msg = f\"{len(approved)} Approved\"\n                if len(rejected) > 0:\n                    msg += f\", {len(rejected)} Rejected\"\n\n            desc += (\n                f'\\n<li><a href=\"{url}\">{project.name} ({locale.code})</a>: {msg}</li>'\n            )\n\n        return desc + \"\\n</ul>\"\n\n    def handle(self, *args, **options):\n        \"\"\"\n        This command sends notifications about newly reviewed\n        suggestions to the authors of those suggestions.\n\n        The command is designed to run on a daily basis.\n        \"\"\"\n        self.stdout.write(\"Sending review notifications...\")\n\n        # (author) -> (locale, project) -> (approved, rejected)\n        data = defaultdict(lambda: defaultdict(lambda: (list(), list())))\n        start = timezone.now() - timedelta(days=1)\n        for suggestion in Translation.objects.filter(\n            (Q(approved_date__gt=start) | Q(rejected_date__gt=start))\n            & Q(user__profile__review_notifications=True)\n        ):\n            author = suggestion.user\n            locale = suggestion.locale\n            project = suggestion.entity.resource.project\n\n            if suggestion.approved and suggestion.approved_user != author:\n                data[author][(locale, project)][0].append(suggestion.entity.pk)\n            elif suggestion.rejected and suggestion.rejected_user != author:\n                data[author][(locale, project)][1].append(suggestion.entity.pk)\n\n        for author, notifyData in data.items():\n            desc = self.get_description(author, notifyData)\n            notify.send(\n                sender=author,\n                recipient=author,\n                verb=\"has reviewed suggestions\",\n                description=desc,\n            )\n\n        self.stdout.write(f\"Sent {len(data)} review notifications.\")\n", "path": "pontoon/projects/management/commands/send_review_notifications.py"}], "after_files": [{"content": "from collections import defaultdict\nfrom datetime import timedelta\n\nfrom django.core.management.base import BaseCommand\nfrom django.db.models import Q\nfrom django.urls import reverse\nfrom django.utils import timezone\nfrom notifications.signals import notify\nfrom pontoon.base.models import Translation\n\n\nclass Command(BaseCommand):\n    help = \"Notify translators about their newly reviewed suggestions\"\n\n    def get_description(self, notifyData):\n        desc = \"Your suggestions have been reviewed:\\n<ul>\"\n\n        for (locale, project), (approved, rejected) in notifyData.items():\n            url = reverse(\n                \"pontoon.translate\",\n                kwargs={\n                    \"locale\": locale.code,\n                    \"project\": project.slug,\n                    \"resource\": \"all-resources\",\n                },\n            )\n            list = map(str, approved + rejected)\n            url += \"?list=\" + \",\".join(list)\n\n            # Filter out rejections where the author's own suggestion replaced the previous\n            rejected = [x for x in rejected if x not in approved]\n\n            if len(approved) == 0:\n                msg = f\"{len(rejected)} Rejected\"\n            else:\n                msg = f\"{len(approved)} Approved\"\n                if len(rejected) > 0:\n                    msg += f\", {len(rejected)} Rejected\"\n\n            desc += (\n                f'\\n<li><a href=\"{url}\">{project.name} ({locale.code})</a>: {msg}</li>'\n            )\n\n        return desc + \"\\n</ul>\"\n\n    def handle(self, *args, **options):\n        \"\"\"\n        This command sends notifications about newly reviewed\n        suggestions to the authors of those suggestions.\n\n        The command is designed to run on a daily basis.\n        \"\"\"\n        self.stdout.write(\"Sending review notifications...\")\n\n        # (author) -> (locale, project) -> (approved, rejected)\n        data = defaultdict(lambda: defaultdict(lambda: (list(), list())))\n        start = timezone.now() - timedelta(days=1)\n        for suggestion in Translation.objects.filter(\n            (Q(approved_date__gt=start) | Q(rejected_date__gt=start))\n            & Q(user__profile__review_notifications=True)\n        ):\n            author = suggestion.user\n            locale = suggestion.locale\n            project = suggestion.entity.resource.project\n\n            if suggestion.approved and suggestion.approved_user != author:\n                data[author][(locale, project)][0].append(suggestion.entity.pk)\n            elif suggestion.rejected and suggestion.rejected_user != author:\n                data[author][(locale, project)][1].append(suggestion.entity.pk)\n\n        for author, notifyData in data.items():\n            desc = self.get_description(notifyData)\n            notify.send(\n                sender=author,\n                recipient=author,\n                verb=\"has reviewed suggestions\",\n                description=desc,\n            )\n\n        self.stdout.write(f\"Sent {len(data)} review notifications.\")\n", "path": "pontoon/projects/management/commands/send_review_notifications.py"}]}