prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Error message for BP voting ballots crashes with 500
  Sentry Issue: [BACKEND-2BV](https://sentry.io/organizations/tabbycat/issues/1252961179/?referrer=github_integration)
  
  ```
  AttributeError: 'tuple' object has no attribute 'replace'
  (6 additional frame(s) were not displayed)
  ...
    File "django/views/generic/base.py", line 97, in dispatch
      return handler(request, *args, **kwargs)
    File "django/views/generic/base.py", line 158, in get
      context = self.get_context_data(**kwargs)
    File "options/views.py", line 54, in get_context_data
      "configuration to use consensus ballots."))
    File "django/utils/translation/__init__.py", line 79, in gettext
      return _trans.gettext(message)
    File "django/utils/translation/trans_real.py", line 282, in gettext
      eol_message = message.replace('\r\n', '\n').replace('\r', '\n')
  ```
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `tabbycat/options/views.py`
  Content:
  ```
  1 import logging
  2 
  3 from django.contrib import messages
  4 from django.http import Http404
  5 from django.utils.text import slugify
  6 from django.utils.translation import gettext as _
  7 from django.views.generic import TemplateView
  8 from dynamic_preferences.views import PreferenceFormView
  9 
  10 from actionlog.mixins import LogActionMixin
  11 from actionlog.models import ActionLogEntry
  12 from tournaments.mixins import TournamentMixin
  13 from utils.mixins import AdministratorMixin
  14 from utils.misc import reverse_tournament
  15 
  16 from .presets import all_presets, get_preferences_data
  17 from .forms import tournament_preference_form_builder
  18 from .preferences import tournament_preferences_registry
  19 
  20 logger = logging.getLogger(__name__)
  21 
  22 
  23 class TournamentConfigIndexView(AdministratorMixin, TournamentMixin, TemplateView):
  24     template_name = "preferences_index.html"
  25 
  26     def get_preset_options(self):
  27         """Returns a list of all preset classes."""
  28         preset_options = []
  29 
  30         for preset_class in all_presets():
  31             preset_class.slugified_name = slugify(preset_class.__name__)
  32             preset_options.append(preset_class)
  33 
  34         preset_options.sort(key=lambda x: (x.show_in_list, x.name))
  35         return preset_options
  36 
  37     def get_context_data(self, **kwargs):
  38         kwargs["presets"] = self.get_preset_options()
  39         t = self.tournament
  40         if t.pref('teams_in_debate') == 'bp':
  41             if t.pref('ballots_per_debate_prelim') == 'per-adj' or \
  42                t.pref('ballots_per_debate_elim') == 'per-adj':
  43                 error = _(("Your draw rules specify four teams per-debate but ",
  44                            "your ballot setting specifies that adjudicators ",
  45                            "submit independent ballots. These settings ",
  46                            "<strong>are not compatible and will cause results ",
  47                            "entry to crash</strong>. You need to go back to ",
  48                            "the Debate Rules settings and change your ",
  49                            "configuration to use consensus ballots."))
  50                 messages.error(self.request, error)
  51 
  52         return super().get_context_data(**kwargs)
  53 
  54 
  55 class TournamentPreferenceFormView(AdministratorMixin, LogActionMixin, TournamentMixin, PreferenceFormView):
  56     registry = tournament_preferences_registry
  57     section = None
  58     template_name = "preferences_section_set.html"
  59 
  60     action_log_type = ActionLogEntry.ACTION_TYPE_OPTIONS_EDIT
  61 
  62     def form_valid(self, *args, **kwargs):
  63         messages.success(self.request, _("Tournament options (%(section)s) saved.") % {'section': self.section.verbose_name})
  64         return super().form_valid(*args, **kwargs)
  65 
  66     def get_success_url(self):
  67         return reverse_tournament('options-tournament-index', self.tournament)
  68 
  69     def get_form_class(self, *args, **kwargs):
  70         section = self.kwargs.get('section', None)
  71         form_class = tournament_preference_form_builder(instance=self.tournament, section=section)
  72         return form_class
  73 
  74 
  75 class ConfirmTournamentPreferencesView(AdministratorMixin, TournamentMixin, TemplateView):
  76     template_name = "preferences_presets_confirm.html"
  77 
  78     def get_selected_preset(self):
  79         preset_name = self.kwargs["preset_name"]
  80         # Retrieve the class that matches the name
  81         selected_presets = [x for x in all_presets() if slugify(x.__name__) == preset_name]
  82         if len(selected_presets) == 0:
  83             logger.warning("Could not find preset: %s", preset_name)
  84             raise Http404("Preset {!r} no found.".format(preset_name))
  85         elif len(selected_presets) > 1:
  86             logger.warning("Found more than one preset for %s", preset_name)
  87         return selected_presets[0]
  88 
  89     def get_context_data(self, **kwargs):
  90         selected_preset = self.get_selected_preset()
  91         preset_preferences = get_preferences_data(selected_preset, self.tournament)
  92         kwargs["preset_title"] = selected_preset.name
  93         kwargs["preset_name"] = self.kwargs["preset_name"]
  94         kwargs["changed_preferences"] = [p for p in preset_preferences if p['changed']]
  95         kwargs["unchanged_preferences"] = [p for p in preset_preferences if not p['changed']]
  96         return super().get_context_data(**kwargs)
  97 
  98     def get_template_names(self):
  99         if self.request.method == 'GET':
  100             return ["preferences_presets_confirm.html"]
  101         else:
  102             return ["preferences_presets_complete.html"]
  103 
  104     def save_presets(self):
  105         selected_preset = self.get_selected_preset()
  106         preset_preferences = get_preferences_data(selected_preset, self.tournament)
  107 
  108         for pref in preset_preferences:
  109             self.tournament.preferences[pref['key']] = pref['new_value']
  110 
  111         ActionLogEntry.objects.log(type=ActionLogEntry.ACTION_TYPE_OPTIONS_EDIT,
  112                 user=self.request.user, tournament=self.tournament, content_object=self.tournament)
  113         messages.success(self.request, _("Tournament options saved according to preset "
  114                 "%(name)s.") % {'name': selected_preset.name})
  115 
  116     def post(self, request, *args, **kwargs):
  117         context = self.get_context_data(**kwargs)
  118         self.save_presets()
  119         return self.render_to_response(context)
  120
  ```
  --- 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/tabbycat/options/views.py b/tabbycat/options/views.py
  --- a/tabbycat/options/views.py
  +++ b/tabbycat/options/views.py
  @@ -40,13 +40,13 @@
           if t.pref('teams_in_debate') == 'bp':
               if t.pref('ballots_per_debate_prelim') == 'per-adj' or \
                  t.pref('ballots_per_debate_elim') == 'per-adj':
  -                error = _(("Your draw rules specify four teams per-debate but ",
  -                           "your ballot setting specifies that adjudicators ",
  -                           "submit independent ballots. These settings ",
  -                           "<strong>are not compatible and will cause results ",
  -                           "entry to crash</strong>. You need to go back to ",
  -                           "the Debate Rules settings and change your ",
  -                           "configuration to use consensus ballots."))
  +                error = _("Your draw rules specify four teams per-debate but "
  +                          "your ballot setting specifies that adjudicators "
  +                          "submit independent ballots. These settings "
  +                          "<strong>are not compatible and will cause results "
  +                          "entry to crash</strong>. You need to go back to "
  +                          "the Debate Rules settings and change your "
  +                          "configuration to use consensus ballots.")
                   messages.error(self.request, error)
   
           return super().get_context_data(**kwargs)
  
verification_info: {"golden_diff": "diff --git a/tabbycat/options/views.py b/tabbycat/options/views.py\n--- a/tabbycat/options/views.py\n+++ b/tabbycat/options/views.py\n@@ -40,13 +40,13 @@\n         if t.pref('teams_in_debate') == 'bp':\n             if t.pref('ballots_per_debate_prelim') == 'per-adj' or \\\n                t.pref('ballots_per_debate_elim') == 'per-adj':\n-                error = _((\"Your draw rules specify four teams per-debate but \",\n-                           \"your ballot setting specifies that adjudicators \",\n-                           \"submit independent ballots. These settings \",\n-                           \"<strong>are not compatible and will cause results \",\n-                           \"entry to crash</strong>. You need to go back to \",\n-                           \"the Debate Rules settings and change your \",\n-                           \"configuration to use consensus ballots.\"))\n+                error = _(\"Your draw rules specify four teams per-debate but \"\n+                          \"your ballot setting specifies that adjudicators \"\n+                          \"submit independent ballots. These settings \"\n+                          \"<strong>are not compatible and will cause results \"\n+                          \"entry to crash</strong>. You need to go back to \"\n+                          \"the Debate Rules settings and change your \"\n+                          \"configuration to use consensus ballots.\")\n                 messages.error(self.request, error)\n \n         return super().get_context_data(**kwargs)\n", "issue": "Error message for BP voting ballots crashes with 500\nSentry Issue: [BACKEND-2BV](https://sentry.io/organizations/tabbycat/issues/1252961179/?referrer=github_integration)\n\n```\nAttributeError: 'tuple' object has no attribute 'replace'\n(6 additional frame(s) were not displayed)\n...\n  File \"django/views/generic/base.py\", line 97, in dispatch\n    return handler(request, *args, **kwargs)\n  File \"django/views/generic/base.py\", line 158, in get\n    context = self.get_context_data(**kwargs)\n  File \"options/views.py\", line 54, in get_context_data\n    \"configuration to use consensus ballots.\"))\n  File \"django/utils/translation/__init__.py\", line 79, in gettext\n    return _trans.gettext(message)\n  File \"django/utils/translation/trans_real.py\", line 282, in gettext\n    eol_message = message.replace('\\r\\n', '\\n').replace('\\r', '\\n')\n```\n", "before_files": [{"content": "import logging\n\nfrom django.contrib import messages\nfrom django.http import Http404\nfrom django.utils.text import slugify\nfrom django.utils.translation import gettext as _\nfrom django.views.generic import TemplateView\nfrom dynamic_preferences.views import PreferenceFormView\n\nfrom actionlog.mixins import LogActionMixin\nfrom actionlog.models import ActionLogEntry\nfrom tournaments.mixins import TournamentMixin\nfrom utils.mixins import AdministratorMixin\nfrom utils.misc import reverse_tournament\n\nfrom .presets import all_presets, get_preferences_data\nfrom .forms import tournament_preference_form_builder\nfrom .preferences import tournament_preferences_registry\n\nlogger = logging.getLogger(__name__)\n\n\nclass TournamentConfigIndexView(AdministratorMixin, TournamentMixin, TemplateView):\n    template_name = \"preferences_index.html\"\n\n    def get_preset_options(self):\n        \"\"\"Returns a list of all preset classes.\"\"\"\n        preset_options = []\n\n        for preset_class in all_presets():\n            preset_class.slugified_name = slugify(preset_class.__name__)\n            preset_options.append(preset_class)\n\n        preset_options.sort(key=lambda x: (x.show_in_list, x.name))\n        return preset_options\n\n    def get_context_data(self, **kwargs):\n        kwargs[\"presets\"] = self.get_preset_options()\n        t = self.tournament\n        if t.pref('teams_in_debate') == 'bp':\n            if t.pref('ballots_per_debate_prelim') == 'per-adj' or \\\n               t.pref('ballots_per_debate_elim') == 'per-adj':\n                error = _((\"Your draw rules specify four teams per-debate but \",\n                           \"your ballot setting specifies that adjudicators \",\n                           \"submit independent ballots. These settings \",\n                           \"<strong>are not compatible and will cause results \",\n                           \"entry to crash</strong>. You need to go back to \",\n                           \"the Debate Rules settings and change your \",\n                           \"configuration to use consensus ballots.\"))\n                messages.error(self.request, error)\n\n        return super().get_context_data(**kwargs)\n\n\nclass TournamentPreferenceFormView(AdministratorMixin, LogActionMixin, TournamentMixin, PreferenceFormView):\n    registry = tournament_preferences_registry\n    section = None\n    template_name = \"preferences_section_set.html\"\n\n    action_log_type = ActionLogEntry.ACTION_TYPE_OPTIONS_EDIT\n\n    def form_valid(self, *args, **kwargs):\n        messages.success(self.request, _(\"Tournament options (%(section)s) saved.\") % {'section': self.section.verbose_name})\n        return super().form_valid(*args, **kwargs)\n\n    def get_success_url(self):\n        return reverse_tournament('options-tournament-index', self.tournament)\n\n    def get_form_class(self, *args, **kwargs):\n        section = self.kwargs.get('section', None)\n        form_class = tournament_preference_form_builder(instance=self.tournament, section=section)\n        return form_class\n\n\nclass ConfirmTournamentPreferencesView(AdministratorMixin, TournamentMixin, TemplateView):\n    template_name = \"preferences_presets_confirm.html\"\n\n    def get_selected_preset(self):\n        preset_name = self.kwargs[\"preset_name\"]\n        # Retrieve the class that matches the name\n        selected_presets = [x for x in all_presets() if slugify(x.__name__) == preset_name]\n        if len(selected_presets) == 0:\n            logger.warning(\"Could not find preset: %s\", preset_name)\n            raise Http404(\"Preset {!r} no found.\".format(preset_name))\n        elif len(selected_presets) > 1:\n            logger.warning(\"Found more than one preset for %s\", preset_name)\n        return selected_presets[0]\n\n    def get_context_data(self, **kwargs):\n        selected_preset = self.get_selected_preset()\n        preset_preferences = get_preferences_data(selected_preset, self.tournament)\n        kwargs[\"preset_title\"] = selected_preset.name\n        kwargs[\"preset_name\"] = self.kwargs[\"preset_name\"]\n        kwargs[\"changed_preferences\"] = [p for p in preset_preferences if p['changed']]\n        kwargs[\"unchanged_preferences\"] = [p for p in preset_preferences if not p['changed']]\n        return super().get_context_data(**kwargs)\n\n    def get_template_names(self):\n        if self.request.method == 'GET':\n            return [\"preferences_presets_confirm.html\"]\n        else:\n            return [\"preferences_presets_complete.html\"]\n\n    def save_presets(self):\n        selected_preset = self.get_selected_preset()\n        preset_preferences = get_preferences_data(selected_preset, self.tournament)\n\n        for pref in preset_preferences:\n            self.tournament.preferences[pref['key']] = pref['new_value']\n\n        ActionLogEntry.objects.log(type=ActionLogEntry.ACTION_TYPE_OPTIONS_EDIT,\n                user=self.request.user, tournament=self.tournament, content_object=self.tournament)\n        messages.success(self.request, _(\"Tournament options saved according to preset \"\n                \"%(name)s.\") % {'name': selected_preset.name})\n\n    def post(self, request, *args, **kwargs):\n        context = self.get_context_data(**kwargs)\n        self.save_presets()\n        return self.render_to_response(context)\n", "path": "tabbycat/options/views.py"}], "after_files": [{"content": "import logging\n\nfrom django.contrib import messages\nfrom django.http import Http404\nfrom django.utils.text import slugify\nfrom django.utils.translation import gettext as _\nfrom django.views.generic import TemplateView\nfrom dynamic_preferences.views import PreferenceFormView\n\nfrom actionlog.mixins import LogActionMixin\nfrom actionlog.models import ActionLogEntry\nfrom tournaments.mixins import TournamentMixin\nfrom utils.mixins import AdministratorMixin\nfrom utils.misc import reverse_tournament\n\nfrom .presets import all_presets, get_preferences_data\nfrom .forms import tournament_preference_form_builder\nfrom .preferences import tournament_preferences_registry\n\nlogger = logging.getLogger(__name__)\n\n\nclass TournamentConfigIndexView(AdministratorMixin, TournamentMixin, TemplateView):\n    template_name = \"preferences_index.html\"\n\n    def get_preset_options(self):\n        \"\"\"Returns a list of all preset classes.\"\"\"\n        preset_options = []\n\n        for preset_class in all_presets():\n            preset_class.slugified_name = slugify(preset_class.__name__)\n            preset_options.append(preset_class)\n\n        preset_options.sort(key=lambda x: (x.show_in_list, x.name))\n        return preset_options\n\n    def get_context_data(self, **kwargs):\n        kwargs[\"presets\"] = self.get_preset_options()\n        t = self.tournament\n        if t.pref('teams_in_debate') == 'bp':\n            if t.pref('ballots_per_debate_prelim') == 'per-adj' or \\\n               t.pref('ballots_per_debate_elim') == 'per-adj':\n                error = _(\"Your draw rules specify four teams per-debate but \"\n                          \"your ballot setting specifies that adjudicators \"\n                          \"submit independent ballots. These settings \"\n                          \"<strong>are not compatible and will cause results \"\n                          \"entry to crash</strong>. You need to go back to \"\n                          \"the Debate Rules settings and change your \"\n                          \"configuration to use consensus ballots.\")\n                messages.error(self.request, error)\n\n        return super().get_context_data(**kwargs)\n\n\nclass TournamentPreferenceFormView(AdministratorMixin, LogActionMixin, TournamentMixin, PreferenceFormView):\n    registry = tournament_preferences_registry\n    section = None\n    template_name = \"preferences_section_set.html\"\n\n    action_log_type = ActionLogEntry.ACTION_TYPE_OPTIONS_EDIT\n\n    def form_valid(self, *args, **kwargs):\n        messages.success(self.request, _(\"Tournament options (%(section)s) saved.\") % {'section': self.section.verbose_name})\n        return super().form_valid(*args, **kwargs)\n\n    def get_success_url(self):\n        return reverse_tournament('options-tournament-index', self.tournament)\n\n    def get_form_class(self, *args, **kwargs):\n        section = self.kwargs.get('section', None)\n        form_class = tournament_preference_form_builder(instance=self.tournament, section=section)\n        return form_class\n\n\nclass ConfirmTournamentPreferencesView(AdministratorMixin, TournamentMixin, TemplateView):\n    template_name = \"preferences_presets_confirm.html\"\n\n    def get_selected_preset(self):\n        preset_name = self.kwargs[\"preset_name\"]\n        # Retrieve the class that matches the name\n        selected_presets = [x for x in all_presets() if slugify(x.__name__) == preset_name]\n        if len(selected_presets) == 0:\n            logger.warning(\"Could not find preset: %s\", preset_name)\n            raise Http404(\"Preset {!r} no found.\".format(preset_name))\n        elif len(selected_presets) > 1:\n            logger.warning(\"Found more than one preset for %s\", preset_name)\n        return selected_presets[0]\n\n    def get_context_data(self, **kwargs):\n        selected_preset = self.get_selected_preset()\n        preset_preferences = get_preferences_data(selected_preset, self.tournament)\n        kwargs[\"preset_title\"] = selected_preset.name\n        kwargs[\"preset_name\"] = self.kwargs[\"preset_name\"]\n        kwargs[\"changed_preferences\"] = [p for p in preset_preferences if p['changed']]\n        kwargs[\"unchanged_preferences\"] = [p for p in preset_preferences if not p['changed']]\n        return super().get_context_data(**kwargs)\n\n    def get_template_names(self):\n        if self.request.method == 'GET':\n            return [\"preferences_presets_confirm.html\"]\n        else:\n            return [\"preferences_presets_complete.html\"]\n\n    def save_presets(self):\n        selected_preset = self.get_selected_preset()\n        preset_preferences = get_preferences_data(selected_preset, self.tournament)\n\n        for pref in preset_preferences:\n            self.tournament.preferences[pref['key']] = pref['new_value']\n\n        ActionLogEntry.objects.log(type=ActionLogEntry.ACTION_TYPE_OPTIONS_EDIT,\n                user=self.request.user, tournament=self.tournament, content_object=self.tournament)\n        messages.success(self.request, _(\"Tournament options saved according to preset \"\n                \"%(name)s.\") % {'name': selected_preset.name})\n\n    def post(self, request, *args, **kwargs):\n        context = self.get_context_data(**kwargs)\n        self.save_presets()\n        return self.render_to_response(context)\n", "path": "tabbycat/options/views.py"}]}