We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Reporting of total speaks in emails uses integers rather than floats
This was a bug report to the FB page, just reposting here for better tracking and if anyone else wants to look at it. Haven't tried to reproduce/look at the code but the report seems clear

In the (default?) email template that is sent to adjs the line *Proposition: Team A (Loss - X total speaks)* reports *X total speaks* as an integer which can lead to incorrect totals in formats with half points. 

--- 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/notifications/utils.py`
Content:
```
1 """Email generator functions
2 
3 These functions assemble the necessary arguments to be parsed in email templates
4 to be sent to relevant parties. All these functions return a tuple with the first
5 element being a context dictionary with the available variables to be parsed in
6 the message. The second element is the Person object. All these functions are
7 called by NotificationQueueConsumer, which inserts the variables into a message,
8 using the participant object to fetch their email address and to record.
9 
10 Objects should be fetched from the database here as it is an asyncronous process,
11 thus the object itself cannot be passed.
12 """
13 
14 from django.utils.safestring import mark_safe
15 from django.utils.translation import gettext as _
16 
17 from adjallocation.allocation import AdjudicatorAllocation
18 from draw.models import Debate
19 from results.result import BaseConsensusDebateResultWithSpeakers, DebateResult, VotingDebateResult
20 from results.utils import side_and_position_names
21 from options.utils import use_team_code_names
22 from participants.models import Person
23 from participants.prefetch import populate_win_counts
24 from tournaments.models import Round, Tournament
25 
26 
27 adj_position_names = {
28     AdjudicatorAllocation.POSITION_CHAIR: _("the chair"),
29     AdjudicatorAllocation.POSITION_ONLY: _("the only"),
30     AdjudicatorAllocation.POSITION_PANELLIST: _("a panellist"),
31     AdjudicatorAllocation.POSITION_TRAINEE: _("a trainee"),
32 }
33 
34 
35 def _assemble_panel(adjs):
36     adj_string = []
37     for adj, pos in adjs:
38         adj_string.append("%s (%s)" % (adj.name, adj_position_names[pos]))
39 
40     return ", ".join(adj_string)
41 
42 
43 def adjudicator_assignment_email_generator(to, url, round_id):
44     emails = []
45     round = Round.objects.get(id=round_id)
46     tournament = round.tournament
47     draw = round.debate_set_with_prefetches(speakers=False).all()
48     use_codes = use_team_code_names(tournament, False)
49 
50     for debate in draw:
51         matchup = debate.matchup_codes if use_codes else debate.matchup
52         context = {
53             'ROUND': round.name,
54             'VENUE': debate.venue.display_name if debate.venue is not None else _("TBA"),
55             'PANEL': _assemble_panel(debate.adjudicators.with_positions()),
56             'DRAW': matchup
57         }
58 
59         for adj, pos in debate.adjudicators.with_positions():
60             try:
61                 to.remove(adj.id)
62             except ValueError:
63                 continue
64 
65             context_user = context.copy()
66             context_user['USER'] = adj.name
67             context_user['POSITION'] = adj_position_names[pos]
68 
69             if adj.url_key:
70                 context_user['URL'] = url + adj.url_key + '/'
71 
72             emails.append((context_user, adj))
73 
74     return emails
75 
76 
77 def randomized_url_email_generator(to, url, tournament_id):
78     emails = []
79     tournament = Tournament.objects.get(id=tournament_id)
80 
81     for instance in tournament.participants:
82         try:
83             to.remove(instance.id)
84         except ValueError:
85             continue
86         url_ind = url + instance.url_key + '/'
87 
88         variables = {'USER': instance.name, 'URL': url_ind, 'KEY': instance.url_key, 'TOURN': str(tournament)}
89 
90         emails.append((variables, instance))
91 
92     return emails
93 
94 
95 def ballots_email_generator(to, debate_id):
96     emails = []
97     debate = Debate.objects.get(id=debate_id)
98     tournament = debate.round.tournament
99     results = DebateResult(debate.confirmed_ballot)
100     round_name = _("%(tournament)s %(round)s @ %(room)s") % {'tournament': str(tournament),
101                                                              'round': debate.round.name, 'room': debate.venue.name}
102 
103     use_codes = use_team_code_names(debate.round.tournament, False)
104 
105     def _create_ballot(result, scoresheet):
106         ballot = "<ul>"
107 
108         for side, (side_name, pos_names) in zip(tournament.sides, side_and_position_names(tournament)):
109             side_string = ""
110             if tournament.pref('teams_in_debate') == 'bp':
111                 side_string += _("<li>%(side)s: %(team)s (%(points)d points with %(speaks)d total speaks)")
112                 points = 4 - scoresheet.rank(side)
113             else:
114                 side_string += _("<li>%(side)s: %(team)s (%(points)s - %(speaks)d total speaks)")
115                 points = _("Win") if side == scoresheet.winner() else _("Loss")
116 
117             ballot += side_string % {
118                 'side': side_name,
119                 'team': result.debateteams[side].team.code_name if use_codes else result.debateteams[side].team.short_name,
120                 'speaks': scoresheet.get_total(side),
121                 'points': points
122             }
123 
124             ballot += "<ul>"
125 
126             for pos, pos_name in zip(tournament.positions, pos_names):
127                 ballot += _("<li>%(pos)s: %(speaker)s (%(score)s)</li>") % {
128                     'pos': pos_name,
129                     'speaker': result.get_speaker(side, pos).name,
130                     'score': scoresheet.get_score(side, pos)
131                 }
132 
133             ballot += "</ul></li>"
134 
135         ballot += "</ul>"
136 
137         return mark_safe(ballot)
138 
139     if isinstance(results, VotingDebateResult):
140         for (adj, ballot) in results.scoresheets.items():
141             if adj.email is None:
142                 continue
143 
144             context = {'DEBATE': round_name, 'USER': adj.name, 'SCORES': _create_ballot(results, ballot)}
145             emails.append((context, adj))
146     elif isinstance(results, BaseConsensusDebateResultWithSpeakers):
147         context = {'DEBATE': round_name, 'SCORES': _create_ballot(results, results.scoresheet)}
148 
149         for adj in debate.debateadjudicator_set.all():
150             if adj.adjudicator.email is None:
151                 continue
152 
153             context_user = context.copy()
154             context_user['USER'] = adj.adjudicator.name
155 
156             emails.append((context_user, adj.adjudicator))
157 
158     return emails
159 
160 
161 def standings_email_generator(to, url, round_id):
162     emails = []
163     round = Round.objects.get(id=round_id)
164     tournament = round.tournament
165 
166     teams = round.active_teams.prefetch_related('speaker_set')
167     populate_win_counts(teams)
168 
169     context = {
170         'TOURN': str(tournament),
171         'ROUND': round.name,
172         'URL': url if tournament.pref('public_team_standings') else ""
173     }
174 
175     for team in teams:
176         context_team = context.copy()
177         context_team['POINTS'] = str(team.points_count)
178         context_team['TEAM'] = team.short_name
179 
180         for speaker in team.speaker_set.all():
181             try:
182                 to.remove(speaker.id)
183             except ValueError:
184                 continue
185 
186             context_user = context_team.copy()
187             context_user['USER'] = speaker.name
188 
189             emails.append((context_user, speaker))
190 
191     return emails
192 
193 
194 def motion_release_email_generator(to, round_id):
195     emails = []
196     round = Round.objects.get(id=round_id)
197 
198     def _create_motion_list():
199         motion_list = "<ul>"
200         for motion in round.motion_set.all():
201             motion_list += _("<li>%(text)s (%(ref)s)</li>") % {'text': motion.text, 'ref': motion.reference}
202 
203             if motion.info_slide:
204                 motion_list += "   %s\n" % (motion.info_slide)
205 
206         motion_list += "</ul>"
207 
208         return mark_safe(motion_list)
209 
210     context = {
211         'TOURN': str(round.tournament),
212         'ROUND': round.name,
213         'MOTIONS': _create_motion_list()
214     }
215 
216     people = Person.objects.filter(id__in=to)
217     for person in people:
218         context_user = context.copy()
219         context_user['USER'] = person.name
220 
221         emails.append((context_user, person))
222 
223     return emails
224 
225 
226 def team_speaker_email_generator(to, tournament_id):
227     emails = []
228     tournament = Tournament.objects.get(id=tournament_id)
229 
230     for team in tournament.team_set.all().prefetch_related('speaker_set', 'break_categories').select_related('institution'):
231         context = {
232             'TOURN': str(tournament),
233             'SHORT': team.short_name,
234             'LONG': team.long_name,
235             'CODE': team.code_name,
236             'BREAK': _(", ").join([breakq.name for breakq in team.break_categories.all()]),
237             'SPEAKERS': _(", ").join([p.name for p in team.speaker_set.all()]),
238             'INSTITUTION': str(team.institution),
239             'EMOJI': team.emoji
240         }
241 
242         for speaker in team.speakers:
243             try:
244                 to.remove(speaker.id)
245             except ValueError:
246                 continue
247 
248             context_user = context.copy()
249             context_user['USER'] = speaker.name
250 
251             emails.append((context_user, speaker))
252 
253     return emails
254 
255 
256 def team_draw_email_generator(to, url, round_id):
257     emails = []
258     round = Round.objects.get(id=round_id)
259     tournament = round.tournament
260     draw = round.debate_set_with_prefetches(speakers=True).all()
261     use_codes = use_team_code_names(tournament, False)
262 
263     for debate in draw:
264         matchup = debate.matchup_codes if use_codes else debate.matchup
265         context = {
266             'ROUND': round.name,
267             'VENUE': debate.venue.name,
268             'PANEL': _assemble_panel(debate.adjudicators.with_positions()),
269             'DRAW': matchup
270         }
271 
272         for dt in debate.debateteam_set.all():
273             context_team = context.copy()
274             context_team['TEAM'] = dt.team.code_name if use_codes else dt.team.short_name
275             context_team['SIDE'] = dt.get_side_name(tournament=tournament)
276 
277             for speaker in dt.team.speakers:
278                 try:
279                     to.remove(speaker.id)
280                 except ValueError:
281                     continue
282 
283                 context_user = context_team.copy()
284                 context_user['USER'] = speaker.name
285 
286                 emails.append((context_user, speaker))
287 
288     return emails
289
```
--- 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/notifications/utils.py b/tabbycat/notifications/utils.py
--- a/tabbycat/notifications/utils.py
+++ b/tabbycat/notifications/utils.py
@@ -11,6 +11,7 @@
 thus the object itself cannot be passed.
 """
 
+from django.utils import formats
 from django.utils.safestring import mark_safe
 from django.utils.translation import gettext as _
 
@@ -108,16 +109,16 @@
         for side, (side_name, pos_names) in zip(tournament.sides, side_and_position_names(tournament)):
             side_string = ""
             if tournament.pref('teams_in_debate') == 'bp':
-                side_string += _("<li>%(side)s: %(team)s (%(points)d points with %(speaks)d total speaks)")
+                side_string += _("<li>%(side)s: %(team)s (%(points)d points with %(speaks)s total speaks)")
                 points = 4 - scoresheet.rank(side)
             else:
-                side_string += _("<li>%(side)s: %(team)s (%(points)s - %(speaks)d total speaks)")
+                side_string += _("<li>%(side)s: %(team)s (%(points)s - %(speaks)s total speaks)")
                 points = _("Win") if side == scoresheet.winner() else _("Loss")
 
             ballot += side_string % {
                 'side': side_name,
                 'team': result.debateteams[side].team.code_name if use_codes else result.debateteams[side].team.short_name,
-                'speaks': scoresheet.get_total(side),
+                'speaks': formats.localize(scoresheet.get_total(side)),
                 'points': points
             }
 
@@ -127,7 +128,7 @@
                 ballot += _("<li>%(pos)s: %(speaker)s (%(score)s)</li>") % {
                     'pos': pos_name,
                     'speaker': result.get_speaker(side, pos).name,
-                    'score': scoresheet.get_score(side, pos)
+                    'score': formats.localize(scoresheet.get_score(side, pos))
                 }
 
             ballot += "</ul></li>"

{"golden_diff": "diff --git a/tabbycat/notifications/utils.py b/tabbycat/notifications/utils.py\n--- a/tabbycat/notifications/utils.py\n+++ b/tabbycat/notifications/utils.py\n@@ -11,6 +11,7 @@\n thus the object itself cannot be passed.\n \"\"\"\n \n+from django.utils import formats\n from django.utils.safestring import mark_safe\n from django.utils.translation import gettext as _\n \n@@ -108,16 +109,16 @@\n         for side, (side_name, pos_names) in zip(tournament.sides, side_and_position_names(tournament)):\n             side_string = \"\"\n             if tournament.pref('teams_in_debate') == 'bp':\n-                side_string += _(\"<li>%(side)s: %(team)s (%(points)d points with %(speaks)d total speaks)\")\n+                side_string += _(\"<li>%(side)s: %(team)s (%(points)d points with %(speaks)s total speaks)\")\n                 points = 4 - scoresheet.rank(side)\n             else:\n-                side_string += _(\"<li>%(side)s: %(team)s (%(points)s - %(speaks)d total speaks)\")\n+                side_string += _(\"<li>%(side)s: %(team)s (%(points)s - %(speaks)s total speaks)\")\n                 points = _(\"Win\") if side == scoresheet.winner() else _(\"Loss\")\n \n             ballot += side_string % {\n                 'side': side_name,\n                 'team': result.debateteams[side].team.code_name if use_codes else result.debateteams[side].team.short_name,\n-                'speaks': scoresheet.get_total(side),\n+                'speaks': formats.localize(scoresheet.get_total(side)),\n                 'points': points\n             }\n \n@@ -127,7 +128,7 @@\n                 ballot += _(\"<li>%(pos)s: %(speaker)s (%(score)s)</li>\") % {\n                     'pos': pos_name,\n                     'speaker': result.get_speaker(side, pos).name,\n-                    'score': scoresheet.get_score(side, pos)\n+                    'score': formats.localize(scoresheet.get_score(side, pos))\n                 }\n \n             ballot += \"</ul></li>\"\n", "issue": "Reporting of total speaks in emails uses integers rather than floats\nThis was a bug report to the FB page, just reposting here for better tracking and if anyone else wants to look at it. Haven't tried to reproduce/look at the code but the report seems clear\r\n\r\nIn the (default?) email template that is sent to adjs the line *Proposition: Team A (Loss - X total speaks)* reports *X total speaks* as an integer which can lead to incorrect totals in formats with half points. \n", "before_files": [{"content": "\"\"\"Email generator functions\n\nThese functions assemble the necessary arguments to be parsed in email templates\nto be sent to relevant parties. All these functions return a tuple with the first\nelement being a context dictionary with the available variables to be parsed in\nthe message. The second element is the Person object. All these functions are\ncalled by NotificationQueueConsumer, which inserts the variables into a message,\nusing the participant object to fetch their email address and to record.\n\nObjects should be fetched from the database here as it is an asyncronous process,\nthus the object itself cannot be passed.\n\"\"\"\n\nfrom django.utils.safestring import mark_safe\nfrom django.utils.translation import gettext as _\n\nfrom adjallocation.allocation import AdjudicatorAllocation\nfrom draw.models import Debate\nfrom results.result import BaseConsensusDebateResultWithSpeakers, DebateResult, VotingDebateResult\nfrom results.utils import side_and_position_names\nfrom options.utils import use_team_code_names\nfrom participants.models import Person\nfrom participants.prefetch import populate_win_counts\nfrom tournaments.models import Round, Tournament\n\n\nadj_position_names = {\n    AdjudicatorAllocation.POSITION_CHAIR: _(\"the chair\"),\n    AdjudicatorAllocation.POSITION_ONLY: _(\"the only\"),\n    AdjudicatorAllocation.POSITION_PANELLIST: _(\"a panellist\"),\n    AdjudicatorAllocation.POSITION_TRAINEE: _(\"a trainee\"),\n}\n\n\ndef _assemble_panel(adjs):\n    adj_string = []\n    for adj, pos in adjs:\n        adj_string.append(\"%s (%s)\" % (adj.name, adj_position_names[pos]))\n\n    return \", \".join(adj_string)\n\n\ndef adjudicator_assignment_email_generator(to, url, round_id):\n    emails = []\n    round = Round.objects.get(id=round_id)\n    tournament = round.tournament\n    draw = round.debate_set_with_prefetches(speakers=False).all()\n    use_codes = use_team_code_names(tournament, False)\n\n    for debate in draw:\n        matchup = debate.matchup_codes if use_codes else debate.matchup\n        context = {\n            'ROUND': round.name,\n            'VENUE': debate.venue.display_name if debate.venue is not None else _(\"TBA\"),\n            'PANEL': _assemble_panel(debate.adjudicators.with_positions()),\n            'DRAW': matchup\n        }\n\n        for adj, pos in debate.adjudicators.with_positions():\n            try:\n                to.remove(adj.id)\n            except ValueError:\n                continue\n\n            context_user = context.copy()\n            context_user['USER'] = adj.name\n            context_user['POSITION'] = adj_position_names[pos]\n\n            if adj.url_key:\n                context_user['URL'] = url + adj.url_key + '/'\n\n            emails.append((context_user, adj))\n\n    return emails\n\n\ndef randomized_url_email_generator(to, url, tournament_id):\n    emails = []\n    tournament = Tournament.objects.get(id=tournament_id)\n\n    for instance in tournament.participants:\n        try:\n            to.remove(instance.id)\n        except ValueError:\n            continue\n        url_ind = url + instance.url_key + '/'\n\n        variables = {'USER': instance.name, 'URL': url_ind, 'KEY': instance.url_key, 'TOURN': str(tournament)}\n\n        emails.append((variables, instance))\n\n    return emails\n\n\ndef ballots_email_generator(to, debate_id):\n    emails = []\n    debate = Debate.objects.get(id=debate_id)\n    tournament = debate.round.tournament\n    results = DebateResult(debate.confirmed_ballot)\n    round_name = _(\"%(tournament)s %(round)s @ %(room)s\") % {'tournament': str(tournament),\n                                                             'round': debate.round.name, 'room': debate.venue.name}\n\n    use_codes = use_team_code_names(debate.round.tournament, False)\n\n    def _create_ballot(result, scoresheet):\n        ballot = \"<ul>\"\n\n        for side, (side_name, pos_names) in zip(tournament.sides, side_and_position_names(tournament)):\n            side_string = \"\"\n            if tournament.pref('teams_in_debate') == 'bp':\n                side_string += _(\"<li>%(side)s: %(team)s (%(points)d points with %(speaks)d total speaks)\")\n                points = 4 - scoresheet.rank(side)\n            else:\n                side_string += _(\"<li>%(side)s: %(team)s (%(points)s - %(speaks)d total speaks)\")\n                points = _(\"Win\") if side == scoresheet.winner() else _(\"Loss\")\n\n            ballot += side_string % {\n                'side': side_name,\n                'team': result.debateteams[side].team.code_name if use_codes else result.debateteams[side].team.short_name,\n                'speaks': scoresheet.get_total(side),\n                'points': points\n            }\n\n            ballot += \"<ul>\"\n\n            for pos, pos_name in zip(tournament.positions, pos_names):\n                ballot += _(\"<li>%(pos)s: %(speaker)s (%(score)s)</li>\") % {\n                    'pos': pos_name,\n                    'speaker': result.get_speaker(side, pos).name,\n                    'score': scoresheet.get_score(side, pos)\n                }\n\n            ballot += \"</ul></li>\"\n\n        ballot += \"</ul>\"\n\n        return mark_safe(ballot)\n\n    if isinstance(results, VotingDebateResult):\n        for (adj, ballot) in results.scoresheets.items():\n            if adj.email is None:\n                continue\n\n            context = {'DEBATE': round_name, 'USER': adj.name, 'SCORES': _create_ballot(results, ballot)}\n            emails.append((context, adj))\n    elif isinstance(results, BaseConsensusDebateResultWithSpeakers):\n        context = {'DEBATE': round_name, 'SCORES': _create_ballot(results, results.scoresheet)}\n\n        for adj in debate.debateadjudicator_set.all():\n            if adj.adjudicator.email is None:\n                continue\n\n            context_user = context.copy()\n            context_user['USER'] = adj.adjudicator.name\n\n            emails.append((context_user, adj.adjudicator))\n\n    return emails\n\n\ndef standings_email_generator(to, url, round_id):\n    emails = []\n    round = Round.objects.get(id=round_id)\n    tournament = round.tournament\n\n    teams = round.active_teams.prefetch_related('speaker_set')\n    populate_win_counts(teams)\n\n    context = {\n        'TOURN': str(tournament),\n        'ROUND': round.name,\n        'URL': url if tournament.pref('public_team_standings') else \"\"\n    }\n\n    for team in teams:\n        context_team = context.copy()\n        context_team['POINTS'] = str(team.points_count)\n        context_team['TEAM'] = team.short_name\n\n        for speaker in team.speaker_set.all():\n            try:\n                to.remove(speaker.id)\n            except ValueError:\n                continue\n\n            context_user = context_team.copy()\n            context_user['USER'] = speaker.name\n\n            emails.append((context_user, speaker))\n\n    return emails\n\n\ndef motion_release_email_generator(to, round_id):\n    emails = []\n    round = Round.objects.get(id=round_id)\n\n    def _create_motion_list():\n        motion_list = \"<ul>\"\n        for motion in round.motion_set.all():\n            motion_list += _(\"<li>%(text)s (%(ref)s)</li>\") % {'text': motion.text, 'ref': motion.reference}\n\n            if motion.info_slide:\n                motion_list += \"   %s\\n\" % (motion.info_slide)\n\n        motion_list += \"</ul>\"\n\n        return mark_safe(motion_list)\n\n    context = {\n        'TOURN': str(round.tournament),\n        'ROUND': round.name,\n        'MOTIONS': _create_motion_list()\n    }\n\n    people = Person.objects.filter(id__in=to)\n    for person in people:\n        context_user = context.copy()\n        context_user['USER'] = person.name\n\n        emails.append((context_user, person))\n\n    return emails\n\n\ndef team_speaker_email_generator(to, tournament_id):\n    emails = []\n    tournament = Tournament.objects.get(id=tournament_id)\n\n    for team in tournament.team_set.all().prefetch_related('speaker_set', 'break_categories').select_related('institution'):\n        context = {\n            'TOURN': str(tournament),\n            'SHORT': team.short_name,\n            'LONG': team.long_name,\n            'CODE': team.code_name,\n            'BREAK': _(\", \").join([breakq.name for breakq in team.break_categories.all()]),\n            'SPEAKERS': _(\", \").join([p.name for p in team.speaker_set.all()]),\n            'INSTITUTION': str(team.institution),\n            'EMOJI': team.emoji\n        }\n\n        for speaker in team.speakers:\n            try:\n                to.remove(speaker.id)\n            except ValueError:\n                continue\n\n            context_user = context.copy()\n            context_user['USER'] = speaker.name\n\n            emails.append((context_user, speaker))\n\n    return emails\n\n\ndef team_draw_email_generator(to, url, round_id):\n    emails = []\n    round = Round.objects.get(id=round_id)\n    tournament = round.tournament\n    draw = round.debate_set_with_prefetches(speakers=True).all()\n    use_codes = use_team_code_names(tournament, False)\n\n    for debate in draw:\n        matchup = debate.matchup_codes if use_codes else debate.matchup\n        context = {\n            'ROUND': round.name,\n            'VENUE': debate.venue.name,\n            'PANEL': _assemble_panel(debate.adjudicators.with_positions()),\n            'DRAW': matchup\n        }\n\n        for dt in debate.debateteam_set.all():\n            context_team = context.copy()\n            context_team['TEAM'] = dt.team.code_name if use_codes else dt.team.short_name\n            context_team['SIDE'] = dt.get_side_name(tournament=tournament)\n\n            for speaker in dt.team.speakers:\n                try:\n                    to.remove(speaker.id)\n                except ValueError:\n                    continue\n\n                context_user = context_team.copy()\n                context_user['USER'] = speaker.name\n\n                emails.append((context_user, speaker))\n\n    return emails\n", "path": "tabbycat/notifications/utils.py"}], "after_files": [{"content": "\"\"\"Email generator functions\n\nThese functions assemble the necessary arguments to be parsed in email templates\nto be sent to relevant parties. All these functions return a tuple with the first\nelement being a context dictionary with the available variables to be parsed in\nthe message. The second element is the Person object. All these functions are\ncalled by NotificationQueueConsumer, which inserts the variables into a message,\nusing the participant object to fetch their email address and to record.\n\nObjects should be fetched from the database here as it is an asyncronous process,\nthus the object itself cannot be passed.\n\"\"\"\n\nfrom django.utils import formats\nfrom django.utils.safestring import mark_safe\nfrom django.utils.translation import gettext as _\n\nfrom adjallocation.allocation import AdjudicatorAllocation\nfrom draw.models import Debate\nfrom results.result import BaseConsensusDebateResultWithSpeakers, DebateResult, VotingDebateResult\nfrom results.utils import side_and_position_names\nfrom options.utils import use_team_code_names\nfrom participants.models import Person\nfrom participants.prefetch import populate_win_counts\nfrom tournaments.models import Round, Tournament\n\n\nadj_position_names = {\n    AdjudicatorAllocation.POSITION_CHAIR: _(\"the chair\"),\n    AdjudicatorAllocation.POSITION_ONLY: _(\"the only\"),\n    AdjudicatorAllocation.POSITION_PANELLIST: _(\"a panellist\"),\n    AdjudicatorAllocation.POSITION_TRAINEE: _(\"a trainee\"),\n}\n\n\ndef _assemble_panel(adjs):\n    adj_string = []\n    for adj, pos in adjs:\n        adj_string.append(\"%s (%s)\" % (adj.name, adj_position_names[pos]))\n\n    return \", \".join(adj_string)\n\n\ndef adjudicator_assignment_email_generator(to, url, round_id):\n    emails = []\n    round = Round.objects.get(id=round_id)\n    tournament = round.tournament\n    draw = round.debate_set_with_prefetches(speakers=False).all()\n    use_codes = use_team_code_names(tournament, False)\n\n    for debate in draw:\n        matchup = debate.matchup_codes if use_codes else debate.matchup\n        context = {\n            'ROUND': round.name,\n            'VENUE': debate.venue.display_name if debate.venue is not None else _(\"TBA\"),\n            'PANEL': _assemble_panel(debate.adjudicators.with_positions()),\n            'DRAW': matchup\n        }\n\n        for adj, pos in debate.adjudicators.with_positions():\n            try:\n                to.remove(adj.id)\n            except ValueError:\n                continue\n\n            context_user = context.copy()\n            context_user['USER'] = adj.name\n            context_user['POSITION'] = adj_position_names[pos]\n\n            if adj.url_key:\n                context_user['URL'] = url + adj.url_key + '/'\n\n            emails.append((context_user, adj))\n\n    return emails\n\n\ndef randomized_url_email_generator(to, url, tournament_id):\n    emails = []\n    tournament = Tournament.objects.get(id=tournament_id)\n\n    for instance in tournament.participants:\n        try:\n            to.remove(instance.id)\n        except ValueError:\n            continue\n        url_ind = url + instance.url_key + '/'\n\n        variables = {'USER': instance.name, 'URL': url_ind, 'KEY': instance.url_key, 'TOURN': str(tournament)}\n\n        emails.append((variables, instance))\n\n    return emails\n\n\ndef ballots_email_generator(to, debate_id):\n    emails = []\n    debate = Debate.objects.get(id=debate_id)\n    tournament = debate.round.tournament\n    results = DebateResult(debate.confirmed_ballot)\n    round_name = _(\"%(tournament)s %(round)s @ %(room)s\") % {'tournament': str(tournament),\n                                                             'round': debate.round.name, 'room': debate.venue.name}\n\n    use_codes = use_team_code_names(debate.round.tournament, False)\n\n    def _create_ballot(result, scoresheet):\n        ballot = \"<ul>\"\n\n        for side, (side_name, pos_names) in zip(tournament.sides, side_and_position_names(tournament)):\n            side_string = \"\"\n            if tournament.pref('teams_in_debate') == 'bp':\n                side_string += _(\"<li>%(side)s: %(team)s (%(points)d points with %(speaks)s total speaks)\")\n                points = 4 - scoresheet.rank(side)\n            else:\n                side_string += _(\"<li>%(side)s: %(team)s (%(points)s - %(speaks)s total speaks)\")\n                points = _(\"Win\") if side == scoresheet.winner() else _(\"Loss\")\n\n            ballot += side_string % {\n                'side': side_name,\n                'team': result.debateteams[side].team.code_name if use_codes else result.debateteams[side].team.short_name,\n                'speaks': formats.localize(scoresheet.get_total(side)),\n                'points': points\n            }\n\n            ballot += \"<ul>\"\n\n            for pos, pos_name in zip(tournament.positions, pos_names):\n                ballot += _(\"<li>%(pos)s: %(speaker)s (%(score)s)</li>\") % {\n                    'pos': pos_name,\n                    'speaker': result.get_speaker(side, pos).name,\n                    'score': formats.localize(scoresheet.get_score(side, pos))\n                }\n\n            ballot += \"</ul></li>\"\n\n        ballot += \"</ul>\"\n\n        return mark_safe(ballot)\n\n    if isinstance(results, VotingDebateResult):\n        for (adj, ballot) in results.scoresheets.items():\n            if adj.email is None:\n                continue\n\n            context = {'DEBATE': round_name, 'USER': adj.name, 'SCORES': _create_ballot(results, ballot)}\n            emails.append((context, adj))\n    elif isinstance(results, BaseConsensusDebateResultWithSpeakers):\n        context = {'DEBATE': round_name, 'SCORES': _create_ballot(results, results.scoresheet)}\n\n        for adj in debate.debateadjudicator_set.all():\n            if adj.adjudicator.email is None:\n                continue\n\n            context_user = context.copy()\n            context_user['USER'] = adj.adjudicator.name\n\n            emails.append((context_user, adj.adjudicator))\n\n    return emails\n\n\ndef standings_email_generator(to, url, round_id):\n    emails = []\n    round = Round.objects.get(id=round_id)\n    tournament = round.tournament\n\n    teams = round.active_teams.prefetch_related('speaker_set')\n    populate_win_counts(teams)\n\n    context = {\n        'TOURN': str(tournament),\n        'ROUND': round.name,\n        'URL': url if tournament.pref('public_team_standings') else \"\"\n    }\n\n    for team in teams:\n        context_team = context.copy()\n        context_team['POINTS'] = str(team.points_count)\n        context_team['TEAM'] = team.short_name\n\n        for speaker in team.speaker_set.all():\n            try:\n                to.remove(speaker.id)\n            except ValueError:\n                continue\n\n            context_user = context_team.copy()\n            context_user['USER'] = speaker.name\n\n            emails.append((context_user, speaker))\n\n    return emails\n\n\ndef motion_release_email_generator(to, round_id):\n    emails = []\n    round = Round.objects.get(id=round_id)\n\n    def _create_motion_list():\n        motion_list = \"<ul>\"\n        for motion in round.motion_set.all():\n            motion_list += _(\"<li>%(text)s (%(ref)s)</li>\") % {'text': motion.text, 'ref': motion.reference}\n\n            if motion.info_slide:\n                motion_list += \"   %s\\n\" % (motion.info_slide)\n\n        motion_list += \"</ul>\"\n\n        return mark_safe(motion_list)\n\n    context = {\n        'TOURN': str(round.tournament),\n        'ROUND': round.name,\n        'MOTIONS': _create_motion_list()\n    }\n\n    people = Person.objects.filter(id__in=to)\n    for person in people:\n        context_user = context.copy()\n        context_user['USER'] = person.name\n\n        emails.append((context_user, person))\n\n    return emails\n\n\ndef team_speaker_email_generator(to, tournament_id):\n    emails = []\n    tournament = Tournament.objects.get(id=tournament_id)\n\n    for team in tournament.team_set.all().prefetch_related('speaker_set', 'break_categories').select_related('institution'):\n        context = {\n            'TOURN': str(tournament),\n            'SHORT': team.short_name,\n            'LONG': team.long_name,\n            'CODE': team.code_name,\n            'BREAK': _(\", \").join([breakq.name for breakq in team.break_categories.all()]),\n            'SPEAKERS': _(\", \").join([p.name for p in team.speaker_set.all()]),\n            'INSTITUTION': str(team.institution),\n            'EMOJI': team.emoji\n        }\n\n        for speaker in team.speakers:\n            try:\n                to.remove(speaker.id)\n            except ValueError:\n                continue\n\n            context_user = context.copy()\n            context_user['USER'] = speaker.name\n\n            emails.append((context_user, speaker))\n\n    return emails\n\n\ndef team_draw_email_generator(to, url, round_id):\n    emails = []\n    round = Round.objects.get(id=round_id)\n    tournament = round.tournament\n    draw = round.debate_set_with_prefetches(speakers=True).all()\n    use_codes = use_team_code_names(tournament, False)\n\n    for debate in draw:\n        matchup = debate.matchup_codes if use_codes else debate.matchup\n        context = {\n            'ROUND': round.name,\n            'VENUE': debate.venue.name,\n            'PANEL': _assemble_panel(debate.adjudicators.with_positions()),\n            'DRAW': matchup\n        }\n\n        for dt in debate.debateteam_set.all():\n            context_team = context.copy()\n            context_team['TEAM'] = dt.team.code_name if use_codes else dt.team.short_name\n            context_team['SIDE'] = dt.get_side_name(tournament=tournament)\n\n            for speaker in dt.team.speakers:\n                try:\n                    to.remove(speaker.id)\n                except ValueError:\n                    continue\n\n                context_user = context_team.copy()\n                context_user['USER'] = speaker.name\n\n                emails.append((context_user, speaker))\n\n    return emails\n", "path": "tabbycat/notifications/utils.py"}]}