Repository: TabbycatDebate/tabbycat

Issue:
Email Notification: Add additional information to ballot receipts
Thanks to @tienne-B for his hard work on bringing this excellent new feature to life. I was wondering if it would be helpful to add some additional information to the {{ scores }} variable. 

![capture](https://user-images.githubusercontent.com/3196468/46149126-b1a83b00-c2bd-11e8-8d4f-1228af6535d5.PNG)

It might be useful to include the total scores and ranking win/lose as well. I think judges are more likely to notice an issue here than by reading the individual speaker scores. This may be low priority given the work it might take to develop different display formats or may have intentionally not been done.

I can think of different ways to do this but one could possibly be:

BP: 
1st - CG: Sacred Heart 2 (180)
2nd - CO: St Andrews 2 (170)
3rd - OG: Macleans 2 (160)
4th - OO: St Marys 1 (140)

WSDC:
Winner - Proposition: Germany (250)
Loser - Opposition: France (245)


Discussion:
Good points! It may also be useful to include the roles of each speaker (for people like me who don't remember names well) The ordering should be kept to maintain the order of the scoresheet, instead of being sorted by rank in my view.

What I have would look like:

OG: Macleans 2 (160 - 3rd)
- PM: Person A (80)
- MC: Person B (80)

_etc._

There are many things in this task: changing sides to abbreviations, adding rankings with team scores, and re-ordering teams and variables. All of this is generalizable to every format, and so the display is not a problem :) I'll have something out soon.

Resolving patch:
diff --git a/tabbycat/notifications/utils.py b/tabbycat/notifications/utils.py
--- a/tabbycat/notifications/utils.py
+++ b/tabbycat/notifications/utils.py
@@ -2,9 +2,9 @@
 from django.utils.translation import gettext as _
 
 from adjallocation.allocation import AdjudicatorAllocation
-from adjallocation.models import DebateAdjudicator
 from draw.models import Debate
-from results.result import DebateResult
+from results.result import BaseConsensusDebateResultWithSpeakers, DebateResult, VotingDebateResult
+from results.utils import side_and_position_names
 from options.utils import use_team_code_names
 from participants.prefetch import populate_win_counts
 from tournaments.models import Round, Tournament
@@ -81,36 +81,50 @@ def ballots_email_generator(debate_id):
     emails = []
     debate = Debate.objects.get(id=debate_id)
     tournament = debate.round.tournament
-    ballots = DebateResult(debate.confirmed_ballot).as_dicts()
+    results = DebateResult(debate.confirmed_ballot)
     round_name = _("%(tournament)s %(round)s @ %(room)s") % {'tournament': str(tournament),
                                                              'round': debate.round.name, 'room': debate.venue.name}
 
-    context = {'DEBATE': round_name}
     use_codes = use_team_code_names(debate.round.tournament, False)
 
-    for ballot in ballots:
-        if 'adjudicator' in ballot:
-            judge = ballot['adjudicator']
-        else:
-            judge = debate.debateadjudicator_set.get(type=DebateAdjudicator.TYPE_CHAIR).adjudicator
+    def _create_ballot(result, scoresheet):
+        ballot = ""
 
-        if judge.email is None:
-            continue
+        for side, (side_name, pos_names) in zip(tournament.sides, side_and_position_names(tournament)):
+            ballot += _("%(side)s: %(team)s (%(points)d - %(rank)s)\n") % {
+                'side': side_name,
+                'team': result.debateteams[side].team.code_name if use_codes else result.debateteams[side].team.short_name,
+                'points': scoresheet.get_total(side),
+                'rank': scoresheet.rank(side)
+            }
 
-        scores = ""
-        for team in ballot['teams']:
+            for pos, pos_name in zip(tournament.positions, pos_names):
+                ballot += _("- %(pos)s: %(speaker)s (%(score)s)\n") % {
+                    'pos': pos_name,
+                    'speaker': result.get_speaker(side, pos).name,
+                    'score': scoresheet.get_score(side, pos)
+                }
 
-            team_name = team['team'].code_name if use_codes else team['team'].short_name
-            scores += _("(%(side)s) %(team)s\n") % {'side': team['side'], 'team': team_name}
+        return ballot
 
-            for speaker in team['speakers']:
-                scores += _("- %(debater)s: %(score)s\n") % {'debater': speaker['speaker'], 'score': speaker['score']}
+    if isinstance(results, VotingDebateResult):
+        for (adj, ballot) in results.scoresheets.items():
+            if adj.email is None:
+                continue
+
+            context = {'DEBATE': round_name, 'USER': adj.name, 'SCORES': _create_ballot(results, ballot)}
+            emails.append((context, adj))
+    elif isinstance(results, BaseConsensusDebateResultWithSpeakers):
+        context = {'DEBATE': round_name, 'SCORES': _create_ballot(results, results.scoresheet)}
 
-        context_user = context.copy()
-        context_user['USER'] = judge.name
-        context_user['SCORES'] = scores
+        for adj in debate.debateadjudicator_set.all():
+            if adj.adjudicator.email is None:
+                continue
+
+            context_user = context.copy()
+            context_user['USER'] = adj.adjudicator.name
 
-        emails.append((context, judge))
+            emails.append((context_user, adj.adjudicator))
 
     return emails
 


Test patch: