We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Crash when generating QF draw (WS)
**Running:**  a1ca1a390866199e1884db12c215ddaa867a98dc

When generating the draw for the first elimination round in a WS tournament, I encountered this exception:

```python
[2023-07-09 12:01:47,564] ERROR django.request: Internal Server Error: /xxx-yyz/admin/draw/round/7/create/
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/django/core/handlers/exception.py", line 56, in inner
    response = get_response(request)
  File "/usr/local/lib/python3.9/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/usr/local/lib/python3.9/site-packages/django/views/generic/base.py", line 103, in view
    return self.dispatch(request, *args, **kwargs)
  File "/usr/local/lib/python3.9/site-packages/django/contrib/auth/mixins.py", line 135, in dispatch
    return super().dispatch(request, *args, **kwargs)
  File "/tcd/tabbycat/tournaments/mixins.py", line 125, in dispatch
    return super().dispatch(request, *args, **kwargs)
  File "/usr/local/lib/python3.9/site-packages/django/views/generic/base.py", line 142, in dispatch
    return handler(request, *args, **kwargs)
  File "/tcd/tabbycat/draw/views.py", line 664, in post
    manager.create()
  File "/tcd/tabbycat/draw/manager.py", line 157, in create
    drawer = DrawGenerator(self.teams_in_debate, generator_type, teams,
  File "/tcd/tabbycat/draw/generator/__init__.py", line 93, in DrawGenerator
    return klass(teams, results, rrseq, **kwargs)
  File "/tcd/tabbycat/draw/generator/common.py", line 182, in __init__
    super().__init__(teams, results, rrseq, **kwargs)
  File "/tcd/tabbycat/draw/generator/common.py", line 73, in __init__
    raise ValueError("Unrecognised options: " + ", ".join(unrecognised))
ValueError: Unrecognised options: avoid_conflicts
```

I quickly patched around it like so and we manually confirmed the draw was correct:

```diff
diff --git a/tabbycat/draw/generator/common.py b/tabbycat/draw/generator/common.py
index 2a61de6ea..3d7167aa1 100644
--- a/tabbycat/draw/generator/common.py
+++ b/tabbycat/draw/generator/common.py
@@ -68,9 +68,10 @@ class BaseDrawGenerator:
         # Compute the full dictionary of default options
         self.options = self.BASE_DEFAULT_OPTIONS.copy()
         self.options.update(self.DEFAULT_OPTIONS)
+        print(self.__class__)
         unrecognised = [key for key in kwargs if key not in self.options]
-        if unrecognised:
-            raise ValueError("Unrecognised options: " + ", ".join(unrecognised))
+#        if unrecognised:
+#            raise ValueError("Unrecognised options: " + ", ".join(unrecognised))
         self.options.update(kwargs)
 
     def generate(self):
```

Of course, this is not a fix for the problem, just avoiding the symptoms. **I intend to find the cause of this issue and fix it in the following days**, bu I'm dropping an issue here so I don't forget

--- 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/draw/generator/common.py`
Content:
```
1 import logging
2 
3 from django.utils.translation import gettext as _
4 
5 logger = logging.getLogger(__name__)
6 
7 
8 class BaseDrawError(Exception):
9     pass
10 
11 
12 class DrawUserError(BaseDrawError):
13     """DrawUserError is raised by any DrawGenerator class when a problem that
14     would appear to be user error prevents a draw from being produced.
15     DrawUserErrors are caught by the view class and shown to the user as an
16     error message.
17 
18     Because DrawUserErrors expected and rectifier, the strings that go into them
19     should be internationalised (marked for translation)."""
20     pass
21 
22 
23 class DrawFatalError(BaseDrawError):
24     """DrawAlgorithmError is raised by any DrawGenerator class when a problem
25     that is an error condition that should never (ever) happen prevents a draw
26     from being produced. DrawAlgorithmError are also caught by the view class
27     and shown to the user as an error message. However, because they should
28     never happen, their messages are not internationalised, since that just
29     creates unnecessary work for translators."""
30     pass
31 
32 
33 class BaseDrawGenerator:
34     """Base class for generators for all draw types, for both two-team and BP.
35     """
36 
37     # Subclasses must define BASE_DEFAULT_OPTIONS
38 
39     requires_even_teams = True
40     requires_prev_results = False
41     requires_rrseq = False
42 
43     def __init__(self, teams, results=None, rrseq=None, **kwargs):
44         self.teams = teams
45         self.team_flags = dict()
46         self.results = results
47         self.rrseq = rrseq
48 
49         if self.requires_even_teams:
50             if not len(self.teams) % self.TEAMS_PER_DEBATE == 0:
51                 raise DrawUserError(_("The number of teams presented for the draw was not "
52                         "a multiple of %(num)d.") % {'num': self.TEAMS_PER_DEBATE})
53             if not self.teams:
54                 raise DrawUserError(_("There were no teams for the draw."))
55 
56         if results is None and self.requires_prev_results:
57             raise TypeError("'results' is required for draw of type {0:s}".format(
58                     self.__class__.__name__))
59 
60         if results is not None and not self.requires_prev_results:
61             logger.warning("'results' not required for draw of type %s, will probably be ignored",
62                     self.__class__.__name__)
63 
64         if rrseq is None and self.requires_rrseq:
65             raise TypeError("'rrseq' (round robin sequence) is required for draw of type {0:s}".format(
66                     self.__class__.__name__))
67 
68         # Compute the full dictionary of default options
69         self.options = self.BASE_DEFAULT_OPTIONS.copy()
70         self.options.update(self.DEFAULT_OPTIONS)
71         unrecognised = [key for key in kwargs if key not in self.options]
72         if unrecognised:
73             raise ValueError("Unrecognised options: " + ", ".join(unrecognised))
74         self.options.update(kwargs)
75 
76     def generate(self):
77         """Abstract method."""
78         raise NotImplementedError
79 
80     def get_option_function(self, option_name, option_dict):
81         option = self.options[option_name]
82         if callable(option):
83             return option
84         try:
85             return getattr(self, option_dict[option])
86         except KeyError:
87             raise ValueError("Invalid option for {1}: {0}".format(option, option_name))
88 
89     def add_team_flag(self, team, flag):
90         """Attaches a flag to a team.
91         Child classes may use this when flags should follow teams, but
92         eventually be attached to pairings."""
93         flags = self.team_flags.setdefault(team, list())
94         flags.append(flag)
95 
96     def annotate_team_flags(self, pairings):
97         """Applies the team flags to the pairings given.
98         Child classes that use team flags should call this method as the last
99         thing before the draw is returned."""
100         for pairing in pairings:
101             for team in pairing.teams:
102                 if team in self.team_flags:
103                     pairing.add_team_flags(team, self.team_flags[team])
104 
105     @classmethod
106     def available_options(cls):
107         keys = set(cls.BASE_DEFAULT_OPTIONS.keys())
108         keys |= set(cls.DEFAULT_OPTIONS.keys())
109         return sorted(list(keys))
110 
111     def check_teams_for_attribute(self, name, choices=None, checkfunc=None):
112         """Checks that all teams have the specified attribute, and raises a
113         DrawFatalError if they don't. This should be called during the
114         constructor. Note: Whether to run this check will sometimes be
115         conditional on options supplied to the DrawGenerator. 'name' is the name
116         of the attribute. 'choices', if specified, is a list of allowed values
117         for the attribute.
118         """
119         has_attribute = [hasattr(x, name) for x in self.teams]
120         if not all(has_attribute):
121             offending_teams = has_attribute.count(False)
122             raise DrawFatalError("{0} out of {1} teams don't have a '{name}' attribute.".format(
123                 offending_teams, len(self.teams), name=name))
124 
125         if choices:
126             attribute_value_valid = [getattr(x, name) in choices for x in self.teams]
127         elif checkfunc:
128             attribute_value_valid = [checkfunc(getattr(x, name)) for x in self.teams]
129         else:
130             return
131 
132         if not all(attribute_value_valid):
133             offending_teams = attribute_value_valid.count(False)
134             message = "{0} out of {1} teams have an invalid '{name}' attribute.".format(offending_teams, len(self.teams), name=name)
135             if choices:
136                 message += " Valid choices: " + ", ".join(map(repr, choices))
137             raise DrawFatalError(message)
138 
139 
140 class BasePairDrawGenerator(BaseDrawGenerator):
141     """Base class for generators for all draw types.
142     Options:
143         "side_allocations" - Side allocation method, one of:
144             "balance" - the team that has affirmed less in prior rounds affirms,
145                 or randomly if both teams have affirmed the same number of times.
146                 If used, team objects must have an `side_history` attribute.
147             "preallocated" - teams were pre-allocated sides. If used, teams must
148                 have an 'allocated_side' attribute.
149             "none" - leave sides as they were when the pairings were drawn.
150                 (This is almost never desirable.)
151             "random" - allocate randomly.
152         "avoid_history" - if True, draw tries to avoid pairing teams that have
153             seen each other before, and tries harder if they've seen each other
154             multiple times.
155         "history_penalty" -
156         "avoid_institution" - if True, draw tries to avoid pairing teams that
157             are from the same institution.
158         "side_penalty" - A penalty to apply when optimizing with side balance
159         """
160 
161     BASE_DEFAULT_OPTIONS = {
162         "side_allocations"      : "balance",
163         "avoid_history"         : True,
164         "avoid_institution"     : True,
165         "history_penalty"       : 1e3,
166         "institution_penalty"   : 1,
167         "side_penalty"          : 0,
168         "pullup_debates_penalty": 0,
169         "pairing_penalty"       : 0,
170     }
171 
172     TEAMS_PER_DEBATE = 2
173 
174     requires_even_teams = True
175     requires_prev_results = False
176     requires_rrseq = False
177 
178     # All subclasses must define this with any options that may exist.
179     DEFAULT_OPTIONS = {}
180 
181     def __init__(self, teams, results=None, rrseq=None, **kwargs):
182         super().__init__(teams, results, rrseq, **kwargs)
183 
184         # Check for required team attributes. Subclasses might do more.
185         if self.options["avoid_history"]:
186             self.check_teams_for_attribute("seen", checkfunc=callable)
187         if self.options["avoid_institution"]:
188             self.check_teams_for_attribute("institution")
189 
190     def allocate_sides(self, pairings):
191         if self.options["side_allocations"] == "balance":
192             for pairing in pairings:
193                 pairing.balance_sides()
194         elif self.options["side_allocations"] == "random":
195             for pairing in pairings:
196                 pairing.shuffle_sides()
197         elif self.options["side_allocations"] not in ["none", "preallocated"]:
198             raise ValueError("side_allocations setting not recognized: {0!r}".format(self.options["side_allocations"]))
199 
200 
201 class BaseBPDrawGenerator(BaseDrawGenerator):
202     BASE_DEFAULT_OPTIONS = {}
203     TEAMS_PER_DEBATE = 4
204 
205 
206 class EliminationDrawMixin:
207     """Mixin for elimination draws."""
208 
209     def generate(self):
210         pairings = self.make_pairings()
211         self.shuffle_sides(pairings)
212         return pairings
213 
214     def shuffle_sides(self, pairings):
215         for pairing in pairings:
216             pairing.shuffle_sides()
217 
218     def make_pairings(self):
219         raise NotImplementedError
220 
221 
222 class ManualDrawGenerator(BaseDrawGenerator):
223     """Returns an empty draw.
224     Since this doesn't really do anything, it works for both two-team and BP.
225     """
226     DEFAULT_OPTIONS = {}
227     BASE_DEFAULT_OPTIONS = {}
228     requires_even_teams = False
229     requires_prev_results = False
230 
231     def generate(self):
232         return []
233
```
--- 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/tabbycat/draw/generator/common.py b/tabbycat/draw/generator/common.py
--- a/tabbycat/draw/generator/common.py
+++ b/tabbycat/draw/generator/common.py
@@ -167,6 +167,7 @@
         "side_penalty"          : 0,
         "pullup_debates_penalty": 0,
         "pairing_penalty"       : 0,
+        "avoid_conflicts"       : "off",
     }
 
     TEAMS_PER_DEBATE = 2

{"golden_diff": "diff --git a/tabbycat/draw/generator/common.py b/tabbycat/draw/generator/common.py\n--- a/tabbycat/draw/generator/common.py\n+++ b/tabbycat/draw/generator/common.py\n@@ -167,6 +167,7 @@\n         \"side_penalty\"          : 0,\n         \"pullup_debates_penalty\": 0,\n         \"pairing_penalty\"       : 0,\n+        \"avoid_conflicts\"       : \"off\",\n     }\n \n     TEAMS_PER_DEBATE = 2\n", "issue": "Crash when generating QF draw (WS)\n**Running:**  a1ca1a390866199e1884db12c215ddaa867a98dc\r\n\r\nWhen generating the draw for the first elimination round in a WS tournament, I encountered this exception:\r\n\r\n```python\r\n[2023-07-09 12:01:47,564] ERROR django.request: Internal Server Error: /xxx-yyz/admin/draw/round/7/create/\r\nTraceback (most recent call last):\r\n  File \"/usr/local/lib/python3.9/site-packages/django/core/handlers/exception.py\", line 56, in inner\r\n    response = get_response(request)\r\n  File \"/usr/local/lib/python3.9/site-packages/django/core/handlers/base.py\", line 197, in _get_response\r\n    response = wrapped_callback(request, *callback_args, **callback_kwargs)\r\n  File \"/usr/local/lib/python3.9/site-packages/django/views/generic/base.py\", line 103, in view\r\n    return self.dispatch(request, *args, **kwargs)\r\n  File \"/usr/local/lib/python3.9/site-packages/django/contrib/auth/mixins.py\", line 135, in dispatch\r\n    return super().dispatch(request, *args, **kwargs)\r\n  File \"/tcd/tabbycat/tournaments/mixins.py\", line 125, in dispatch\r\n    return super().dispatch(request, *args, **kwargs)\r\n  File \"/usr/local/lib/python3.9/site-packages/django/views/generic/base.py\", line 142, in dispatch\r\n    return handler(request, *args, **kwargs)\r\n  File \"/tcd/tabbycat/draw/views.py\", line 664, in post\r\n    manager.create()\r\n  File \"/tcd/tabbycat/draw/manager.py\", line 157, in create\r\n    drawer = DrawGenerator(self.teams_in_debate, generator_type, teams,\r\n  File \"/tcd/tabbycat/draw/generator/__init__.py\", line 93, in DrawGenerator\r\n    return klass(teams, results, rrseq, **kwargs)\r\n  File \"/tcd/tabbycat/draw/generator/common.py\", line 182, in __init__\r\n    super().__init__(teams, results, rrseq, **kwargs)\r\n  File \"/tcd/tabbycat/draw/generator/common.py\", line 73, in __init__\r\n    raise ValueError(\"Unrecognised options: \" + \", \".join(unrecognised))\r\nValueError: Unrecognised options: avoid_conflicts\r\n```\r\n\r\nI quickly patched around it like so and we manually confirmed the draw was correct:\r\n\r\n```diff\r\ndiff --git a/tabbycat/draw/generator/common.py b/tabbycat/draw/generator/common.py\r\nindex 2a61de6ea..3d7167aa1 100644\r\n--- a/tabbycat/draw/generator/common.py\r\n+++ b/tabbycat/draw/generator/common.py\r\n@@ -68,9 +68,10 @@ class BaseDrawGenerator:\r\n         # Compute the full dictionary of default options\r\n         self.options = self.BASE_DEFAULT_OPTIONS.copy()\r\n         self.options.update(self.DEFAULT_OPTIONS)\r\n+        print(self.__class__)\r\n         unrecognised = [key for key in kwargs if key not in self.options]\r\n-        if unrecognised:\r\n-            raise ValueError(\"Unrecognised options: \" + \", \".join(unrecognised))\r\n+#        if unrecognised:\r\n+#            raise ValueError(\"Unrecognised options: \" + \", \".join(unrecognised))\r\n         self.options.update(kwargs)\r\n \r\n     def generate(self):\r\n```\r\n\r\nOf course, this is not a fix for the problem, just avoiding the symptoms. **I intend to find the cause of this issue and fix it in the following days**, bu I'm dropping an issue here so I don't forget\n", "before_files": [{"content": "import logging\n\nfrom django.utils.translation import gettext as _\n\nlogger = logging.getLogger(__name__)\n\n\nclass BaseDrawError(Exception):\n    pass\n\n\nclass DrawUserError(BaseDrawError):\n    \"\"\"DrawUserError is raised by any DrawGenerator class when a problem that\n    would appear to be user error prevents a draw from being produced.\n    DrawUserErrors are caught by the view class and shown to the user as an\n    error message.\n\n    Because DrawUserErrors expected and rectifier, the strings that go into them\n    should be internationalised (marked for translation).\"\"\"\n    pass\n\n\nclass DrawFatalError(BaseDrawError):\n    \"\"\"DrawAlgorithmError is raised by any DrawGenerator class when a problem\n    that is an error condition that should never (ever) happen prevents a draw\n    from being produced. DrawAlgorithmError are also caught by the view class\n    and shown to the user as an error message. However, because they should\n    never happen, their messages are not internationalised, since that just\n    creates unnecessary work for translators.\"\"\"\n    pass\n\n\nclass BaseDrawGenerator:\n    \"\"\"Base class for generators for all draw types, for both two-team and BP.\n    \"\"\"\n\n    # Subclasses must define BASE_DEFAULT_OPTIONS\n\n    requires_even_teams = True\n    requires_prev_results = False\n    requires_rrseq = False\n\n    def __init__(self, teams, results=None, rrseq=None, **kwargs):\n        self.teams = teams\n        self.team_flags = dict()\n        self.results = results\n        self.rrseq = rrseq\n\n        if self.requires_even_teams:\n            if not len(self.teams) % self.TEAMS_PER_DEBATE == 0:\n                raise DrawUserError(_(\"The number of teams presented for the draw was not \"\n                        \"a multiple of %(num)d.\") % {'num': self.TEAMS_PER_DEBATE})\n            if not self.teams:\n                raise DrawUserError(_(\"There were no teams for the draw.\"))\n\n        if results is None and self.requires_prev_results:\n            raise TypeError(\"'results' is required for draw of type {0:s}\".format(\n                    self.__class__.__name__))\n\n        if results is not None and not self.requires_prev_results:\n            logger.warning(\"'results' not required for draw of type %s, will probably be ignored\",\n                    self.__class__.__name__)\n\n        if rrseq is None and self.requires_rrseq:\n            raise TypeError(\"'rrseq' (round robin sequence) is required for draw of type {0:s}\".format(\n                    self.__class__.__name__))\n\n        # Compute the full dictionary of default options\n        self.options = self.BASE_DEFAULT_OPTIONS.copy()\n        self.options.update(self.DEFAULT_OPTIONS)\n        unrecognised = [key for key in kwargs if key not in self.options]\n        if unrecognised:\n            raise ValueError(\"Unrecognised options: \" + \", \".join(unrecognised))\n        self.options.update(kwargs)\n\n    def generate(self):\n        \"\"\"Abstract method.\"\"\"\n        raise NotImplementedError\n\n    def get_option_function(self, option_name, option_dict):\n        option = self.options[option_name]\n        if callable(option):\n            return option\n        try:\n            return getattr(self, option_dict[option])\n        except KeyError:\n            raise ValueError(\"Invalid option for {1}: {0}\".format(option, option_name))\n\n    def add_team_flag(self, team, flag):\n        \"\"\"Attaches a flag to a team.\n        Child classes may use this when flags should follow teams, but\n        eventually be attached to pairings.\"\"\"\n        flags = self.team_flags.setdefault(team, list())\n        flags.append(flag)\n\n    def annotate_team_flags(self, pairings):\n        \"\"\"Applies the team flags to the pairings given.\n        Child classes that use team flags should call this method as the last\n        thing before the draw is returned.\"\"\"\n        for pairing in pairings:\n            for team in pairing.teams:\n                if team in self.team_flags:\n                    pairing.add_team_flags(team, self.team_flags[team])\n\n    @classmethod\n    def available_options(cls):\n        keys = set(cls.BASE_DEFAULT_OPTIONS.keys())\n        keys |= set(cls.DEFAULT_OPTIONS.keys())\n        return sorted(list(keys))\n\n    def check_teams_for_attribute(self, name, choices=None, checkfunc=None):\n        \"\"\"Checks that all teams have the specified attribute, and raises a\n        DrawFatalError if they don't. This should be called during the\n        constructor. Note: Whether to run this check will sometimes be\n        conditional on options supplied to the DrawGenerator. 'name' is the name\n        of the attribute. 'choices', if specified, is a list of allowed values\n        for the attribute.\n        \"\"\"\n        has_attribute = [hasattr(x, name) for x in self.teams]\n        if not all(has_attribute):\n            offending_teams = has_attribute.count(False)\n            raise DrawFatalError(\"{0} out of {1} teams don't have a '{name}' attribute.\".format(\n                offending_teams, len(self.teams), name=name))\n\n        if choices:\n            attribute_value_valid = [getattr(x, name) in choices for x in self.teams]\n        elif checkfunc:\n            attribute_value_valid = [checkfunc(getattr(x, name)) for x in self.teams]\n        else:\n            return\n\n        if not all(attribute_value_valid):\n            offending_teams = attribute_value_valid.count(False)\n            message = \"{0} out of {1} teams have an invalid '{name}' attribute.\".format(offending_teams, len(self.teams), name=name)\n            if choices:\n                message += \" Valid choices: \" + \", \".join(map(repr, choices))\n            raise DrawFatalError(message)\n\n\nclass BasePairDrawGenerator(BaseDrawGenerator):\n    \"\"\"Base class for generators for all draw types.\n    Options:\n        \"side_allocations\" - Side allocation method, one of:\n            \"balance\" - the team that has affirmed less in prior rounds affirms,\n                or randomly if both teams have affirmed the same number of times.\n                If used, team objects must have an `side_history` attribute.\n            \"preallocated\" - teams were pre-allocated sides. If used, teams must\n                have an 'allocated_side' attribute.\n            \"none\" - leave sides as they were when the pairings were drawn.\n                (This is almost never desirable.)\n            \"random\" - allocate randomly.\n        \"avoid_history\" - if True, draw tries to avoid pairing teams that have\n            seen each other before, and tries harder if they've seen each other\n            multiple times.\n        \"history_penalty\" -\n        \"avoid_institution\" - if True, draw tries to avoid pairing teams that\n            are from the same institution.\n        \"side_penalty\" - A penalty to apply when optimizing with side balance\n        \"\"\"\n\n    BASE_DEFAULT_OPTIONS = {\n        \"side_allocations\"      : \"balance\",\n        \"avoid_history\"         : True,\n        \"avoid_institution\"     : True,\n        \"history_penalty\"       : 1e3,\n        \"institution_penalty\"   : 1,\n        \"side_penalty\"          : 0,\n        \"pullup_debates_penalty\": 0,\n        \"pairing_penalty\"       : 0,\n    }\n\n    TEAMS_PER_DEBATE = 2\n\n    requires_even_teams = True\n    requires_prev_results = False\n    requires_rrseq = False\n\n    # All subclasses must define this with any options that may exist.\n    DEFAULT_OPTIONS = {}\n\n    def __init__(self, teams, results=None, rrseq=None, **kwargs):\n        super().__init__(teams, results, rrseq, **kwargs)\n\n        # Check for required team attributes. Subclasses might do more.\n        if self.options[\"avoid_history\"]:\n            self.check_teams_for_attribute(\"seen\", checkfunc=callable)\n        if self.options[\"avoid_institution\"]:\n            self.check_teams_for_attribute(\"institution\")\n\n    def allocate_sides(self, pairings):\n        if self.options[\"side_allocations\"] == \"balance\":\n            for pairing in pairings:\n                pairing.balance_sides()\n        elif self.options[\"side_allocations\"] == \"random\":\n            for pairing in pairings:\n                pairing.shuffle_sides()\n        elif self.options[\"side_allocations\"] not in [\"none\", \"preallocated\"]:\n            raise ValueError(\"side_allocations setting not recognized: {0!r}\".format(self.options[\"side_allocations\"]))\n\n\nclass BaseBPDrawGenerator(BaseDrawGenerator):\n    BASE_DEFAULT_OPTIONS = {}\n    TEAMS_PER_DEBATE = 4\n\n\nclass EliminationDrawMixin:\n    \"\"\"Mixin for elimination draws.\"\"\"\n\n    def generate(self):\n        pairings = self.make_pairings()\n        self.shuffle_sides(pairings)\n        return pairings\n\n    def shuffle_sides(self, pairings):\n        for pairing in pairings:\n            pairing.shuffle_sides()\n\n    def make_pairings(self):\n        raise NotImplementedError\n\n\nclass ManualDrawGenerator(BaseDrawGenerator):\n    \"\"\"Returns an empty draw.\n    Since this doesn't really do anything, it works for both two-team and BP.\n    \"\"\"\n    DEFAULT_OPTIONS = {}\n    BASE_DEFAULT_OPTIONS = {}\n    requires_even_teams = False\n    requires_prev_results = False\n\n    def generate(self):\n        return []\n", "path": "tabbycat/draw/generator/common.py"}], "after_files": [{"content": "import logging\n\nfrom django.utils.translation import gettext as _\n\nlogger = logging.getLogger(__name__)\n\n\nclass BaseDrawError(Exception):\n    pass\n\n\nclass DrawUserError(BaseDrawError):\n    \"\"\"DrawUserError is raised by any DrawGenerator class when a problem that\n    would appear to be user error prevents a draw from being produced.\n    DrawUserErrors are caught by the view class and shown to the user as an\n    error message.\n\n    Because DrawUserErrors expected and rectifier, the strings that go into them\n    should be internationalised (marked for translation).\"\"\"\n    pass\n\n\nclass DrawFatalError(BaseDrawError):\n    \"\"\"DrawAlgorithmError is raised by any DrawGenerator class when a problem\n    that is an error condition that should never (ever) happen prevents a draw\n    from being produced. DrawAlgorithmError are also caught by the view class\n    and shown to the user as an error message. However, because they should\n    never happen, their messages are not internationalised, since that just\n    creates unnecessary work for translators.\"\"\"\n    pass\n\n\nclass BaseDrawGenerator:\n    \"\"\"Base class for generators for all draw types, for both two-team and BP.\n    \"\"\"\n\n    # Subclasses must define BASE_DEFAULT_OPTIONS\n\n    requires_even_teams = True\n    requires_prev_results = False\n    requires_rrseq = False\n\n    def __init__(self, teams, results=None, rrseq=None, **kwargs):\n        self.teams = teams\n        self.team_flags = dict()\n        self.results = results\n        self.rrseq = rrseq\n\n        if self.requires_even_teams:\n            if not len(self.teams) % self.TEAMS_PER_DEBATE == 0:\n                raise DrawUserError(_(\"The number of teams presented for the draw was not \"\n                        \"a multiple of %(num)d.\") % {'num': self.TEAMS_PER_DEBATE})\n            if not self.teams:\n                raise DrawUserError(_(\"There were no teams for the draw.\"))\n\n        if results is None and self.requires_prev_results:\n            raise TypeError(\"'results' is required for draw of type {0:s}\".format(\n                    self.__class__.__name__))\n\n        if results is not None and not self.requires_prev_results:\n            logger.warning(\"'results' not required for draw of type %s, will probably be ignored\",\n                    self.__class__.__name__)\n\n        if rrseq is None and self.requires_rrseq:\n            raise TypeError(\"'rrseq' (round robin sequence) is required for draw of type {0:s}\".format(\n                    self.__class__.__name__))\n\n        # Compute the full dictionary of default options\n        self.options = self.BASE_DEFAULT_OPTIONS.copy()\n        self.options.update(self.DEFAULT_OPTIONS)\n        unrecognised = [key for key in kwargs if key not in self.options]\n        if unrecognised:\n            raise ValueError(\"Unrecognised options: \" + \", \".join(unrecognised))\n        self.options.update(kwargs)\n\n    def generate(self):\n        \"\"\"Abstract method.\"\"\"\n        raise NotImplementedError\n\n    def get_option_function(self, option_name, option_dict):\n        option = self.options[option_name]\n        if callable(option):\n            return option\n        try:\n            return getattr(self, option_dict[option])\n        except KeyError:\n            raise ValueError(\"Invalid option for {1}: {0}\".format(option, option_name))\n\n    def add_team_flag(self, team, flag):\n        \"\"\"Attaches a flag to a team.\n        Child classes may use this when flags should follow teams, but\n        eventually be attached to pairings.\"\"\"\n        flags = self.team_flags.setdefault(team, list())\n        flags.append(flag)\n\n    def annotate_team_flags(self, pairings):\n        \"\"\"Applies the team flags to the pairings given.\n        Child classes that use team flags should call this method as the last\n        thing before the draw is returned.\"\"\"\n        for pairing in pairings:\n            for team in pairing.teams:\n                if team in self.team_flags:\n                    pairing.add_team_flags(team, self.team_flags[team])\n\n    @classmethod\n    def available_options(cls):\n        keys = set(cls.BASE_DEFAULT_OPTIONS.keys())\n        keys |= set(cls.DEFAULT_OPTIONS.keys())\n        return sorted(list(keys))\n\n    def check_teams_for_attribute(self, name, choices=None, checkfunc=None):\n        \"\"\"Checks that all teams have the specified attribute, and raises a\n        DrawFatalError if they don't. This should be called during the\n        constructor. Note: Whether to run this check will sometimes be\n        conditional on options supplied to the DrawGenerator. 'name' is the name\n        of the attribute. 'choices', if specified, is a list of allowed values\n        for the attribute.\n        \"\"\"\n        has_attribute = [hasattr(x, name) for x in self.teams]\n        if not all(has_attribute):\n            offending_teams = has_attribute.count(False)\n            raise DrawFatalError(\"{0} out of {1} teams don't have a '{name}' attribute.\".format(\n                offending_teams, len(self.teams), name=name))\n\n        if choices:\n            attribute_value_valid = [getattr(x, name) in choices for x in self.teams]\n        elif checkfunc:\n            attribute_value_valid = [checkfunc(getattr(x, name)) for x in self.teams]\n        else:\n            return\n\n        if not all(attribute_value_valid):\n            offending_teams = attribute_value_valid.count(False)\n            message = \"{0} out of {1} teams have an invalid '{name}' attribute.\".format(offending_teams, len(self.teams), name=name)\n            if choices:\n                message += \" Valid choices: \" + \", \".join(map(repr, choices))\n            raise DrawFatalError(message)\n\n\nclass BasePairDrawGenerator(BaseDrawGenerator):\n    \"\"\"Base class for generators for all draw types.\n    Options:\n        \"side_allocations\" - Side allocation method, one of:\n            \"balance\" - the team that has affirmed less in prior rounds affirms,\n                or randomly if both teams have affirmed the same number of times.\n                If used, team objects must have an `side_history` attribute.\n            \"preallocated\" - teams were pre-allocated sides. If used, teams must\n                have an 'allocated_side' attribute.\n            \"none\" - leave sides as they were when the pairings were drawn.\n                (This is almost never desirable.)\n            \"random\" - allocate randomly.\n        \"avoid_history\" - if True, draw tries to avoid pairing teams that have\n            seen each other before, and tries harder if they've seen each other\n            multiple times.\n        \"history_penalty\" -\n        \"avoid_institution\" - if True, draw tries to avoid pairing teams that\n            are from the same institution.\n        \"side_penalty\" - A penalty to apply when optimizing with side balance\n        \"\"\"\n\n    BASE_DEFAULT_OPTIONS = {\n        \"side_allocations\"      : \"balance\",\n        \"avoid_history\"         : True,\n        \"avoid_institution\"     : True,\n        \"history_penalty\"       : 1e3,\n        \"institution_penalty\"   : 1,\n        \"side_penalty\"          : 0,\n        \"pullup_debates_penalty\": 0,\n        \"pairing_penalty\"       : 0,\n        \"avoid_conflicts\"       : \"off\",\n    }\n\n    TEAMS_PER_DEBATE = 2\n\n    requires_even_teams = True\n    requires_prev_results = False\n    requires_rrseq = False\n\n    # All subclasses must define this with any options that may exist.\n    DEFAULT_OPTIONS = {}\n\n    def __init__(self, teams, results=None, rrseq=None, **kwargs):\n        super().__init__(teams, results, rrseq, **kwargs)\n\n        # Check for required team attributes. Subclasses might do more.\n        if self.options[\"avoid_history\"]:\n            self.check_teams_for_attribute(\"seen\", checkfunc=callable)\n        if self.options[\"avoid_institution\"]:\n            self.check_teams_for_attribute(\"institution\")\n\n    def allocate_sides(self, pairings):\n        if self.options[\"side_allocations\"] == \"balance\":\n            for pairing in pairings:\n                pairing.balance_sides()\n        elif self.options[\"side_allocations\"] == \"random\":\n            for pairing in pairings:\n                pairing.shuffle_sides()\n        elif self.options[\"side_allocations\"] not in [\"none\", \"preallocated\"]:\n            raise ValueError(\"side_allocations setting not recognized: {0!r}\".format(self.options[\"side_allocations\"]))\n\n\nclass BaseBPDrawGenerator(BaseDrawGenerator):\n    BASE_DEFAULT_OPTIONS = {}\n    TEAMS_PER_DEBATE = 4\n\n\nclass EliminationDrawMixin:\n    \"\"\"Mixin for elimination draws.\"\"\"\n\n    def generate(self):\n        pairings = self.make_pairings()\n        self.shuffle_sides(pairings)\n        return pairings\n\n    def shuffle_sides(self, pairings):\n        for pairing in pairings:\n            pairing.shuffle_sides()\n\n    def make_pairings(self):\n        raise NotImplementedError\n\n\nclass ManualDrawGenerator(BaseDrawGenerator):\n    \"\"\"Returns an empty draw.\n    Since this doesn't really do anything, it works for both two-team and BP.\n    \"\"\"\n    DEFAULT_OPTIONS = {}\n    BASE_DEFAULT_OPTIONS = {}\n    requires_even_teams = False\n    requires_prev_results = False\n\n    def generate(self):\n        return []\n", "path": "tabbycat/draw/generator/common.py"}]}