Skip to content

Commit

Permalink
Merge branch 'hotfix/2.7.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
tienne-B committed Nov 5, 2022
2 parents 0e693f2 + e6d20ac commit 3e318f8
Show file tree
Hide file tree
Showing 24 changed files with 140 additions and 95 deletions.
13 changes: 13 additions & 0 deletions .github/CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@
Change Log
==========

2.7.1
-----
*Release date: TBD*

- Escaped values in tables to avoid malicious data
- Fixed crash on loading email dialog for team draws
- Fixed team standing emails not being sent
- Fixed sorting by venue name or priority in the allocator
- Fixed adjudicator private URLs not loading
- Adjudicator feedback tables now properly sortable by number of feedback
- Checkboxes no longer overlap with table headers


2.7.0 (Pixie-bob)
---------
*Release date: 1 October 2022*
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
# The short X.Y version.
version = '2.7'
# The full version, including alpha/beta/rc tags.
release = '2.7.0-dev'
release = '2.7.1'

rst_epilog = """
.. |vrelease| replace:: v{release}
Expand Down
17 changes: 9 additions & 8 deletions tabbycat/adjallocation/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import math
from itertools import combinations, product

from django.utils.html import escape
from django.utils.translation import gettext as _

from participants.models import Adjudicator, Team
Expand All @@ -27,16 +28,16 @@ def adjudicator_conflicts_display(debates):
conflict_messages[debate].append(("danger", _(
"Conflict: <strong>%(adjudicator)s</strong> & <strong>%(team)s</strong> "
"(personal)",
) % {'adjudicator': adj.name, 'team': team.short_name}))
) % {'adjudicator': escape(adj.name), 'team': escape(team.short_name)}))

for institution in conflicts.conflicting_institutions_adj_team(adj, team):
conflict_messages[debate].append(("danger", _(
"Conflict: <strong>%(adjudicator)s</strong> & <strong>%(team)s</strong> "
"via institution <strong>%(institution)s</strong>",
) % {
'adjudicator': adj.name,
'team': team.short_name,
'institution': institution.code,
'adjudicator': escape(adj.name),
'team': escape(team.short_name),
'institution': escape(institution.code),
}))

for adj1, adj2 in combinations(debate.adjudicators.all(), 2):
Expand All @@ -45,16 +46,16 @@ def adjudicator_conflicts_display(debates):
conflict_messages[debate].append(("danger", _(
"Conflict: <strong>%(adjudicator1)s</strong> & <strong>%(adjudicator2)s</strong> "
"(personal)",
) % {'adjudicator1': adj1.name, 'adjudicator2': adj2.name}))
) % {'adjudicator1': escape(adj1.name), 'adjudicator2': escape(adj2.name)}))

for institution in conflicts.conflicting_institutions_adj_adj(adj1, adj2):
conflict_messages[debate].append(("warning", _(
"Conflict: <strong>%(adjudicator1)s</strong> & <strong>%(adjudicator2)s</strong> "
"via institution <strong>%(institution)s</strong>",
) % {
'adjudicator1': adj1.name,
'adjudicator2': adj2.name,
'institution': institution.code,
'adjudicator1': escape(adj1.name),
'adjudicator2': escape(adj2.name),
'institution': escape(institution.code),
}))

return conflict_messages
Expand Down
4 changes: 3 additions & 1 deletion tabbycat/adjfeedback/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,13 @@ def add_base_score_columns(self, adjudicators, editable=False):
'modal': adj.id,
'class': 'edit-base-score',
'tooltip': _("Click to edit base score"),
'sort': adj.base_score,
} for adj in adjudicators]
else:
test_data = [{
'text': self.get_formatted_adj_score(adj.base_score),
'tooltip': _("Assigned base score"),
'sort': adj.base_score,
} for adj in adjudicators]

self.add_column(test_header, test_data)
Expand Down Expand Up @@ -127,7 +129,7 @@ def add_feedback_link_columns(self, adjudicators):
len(adj.feedback_data) - 1,
) % {'count': len(adj.feedback_data) - 1}, # -1 to account for base score
'class': 'view-feedback',
'sort': adj.debates,
'sort': len(adj.feedback_data) - 1,
'link': reverse_tournament('adjfeedback-view-on-adjudicator', self.tournament, kwargs={'pk': adj.pk}),
} for adj in adjudicators]
self.add_column(link_head, link_cell)
Expand Down
2 changes: 1 addition & 1 deletion tabbycat/adjfeedback/templates/feedback_card.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
</div>
<div class="list-group-item h5">
{% if feedback.source_adjudicator %}
{% person_display_name feedback.source_adjudicator tournament as source %}
{% person_display_name feedback.source_adjudicator.adjudicator as source %}
{% blocktrans trimmed with source=source relationship=feedback.source_adjudicator.get_type_display %}
From {{ source }} <span class="text-secondary small">(their {{ relationship }})</span>
{% endblocktrans %}
Expand Down
12 changes: 8 additions & 4 deletions tabbycat/adjfeedback/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from django.db.models import Count, F, Q
from django.http import HttpResponse, JsonResponse
from django.utils import timezone
from django.utils.html import conditional_escape, escape
from django.utils.translation import gettext as _, gettext_lazy, ngettext, ngettext_lazy
from django.views.generic.base import TemplateView, View
from django.views.generic.edit import FormView
Expand Down Expand Up @@ -151,6 +152,7 @@ def get_table(self):
count = adj.feedback_count
feedback_data.append({
'text': ngettext("%(count)d feedback", "%(count)d feedbacks", count) % {'count': count},
'sort': count,
'link': reverse_tournament('adjfeedback-view-on-adjudicator', self.tournament, kwargs={'pk': adj.id}),
})
table.add_column({'key': 'feedbacks', 'title': _("Feedbacks")}, feedback_data)
Expand All @@ -175,6 +177,7 @@ def get_tables(self):
count = team.feedback_count
team_feedback_data.append({
'text': ngettext("%(count)d feedback", "%(count)d feedbacks", count) % {'count': count},
'sort': count,
'link': reverse_tournament('adjfeedback-view-from-team',
tournament,
kwargs={'pk': team.id}),
Expand All @@ -190,6 +193,7 @@ def get_tables(self):
count = adj.feedback_count
adj_feedback_data.append({
'text': ngettext("%(count)d feedback", "%(count)d feedbacks", count) % {'count': count},
'sort': count,
'link': reverse_tournament('adjfeedback-view-from-adjudicator',
tournament,
kwargs={'pk': adj.id}),
Expand Down Expand Up @@ -361,7 +365,7 @@ def get_tables(self):
use_code_names = use_team_code_names_data_entry(self.tournament, self.tabroom)
teams_table = TabbycatTableBuilder(view=self, sort_key="team", title=_("A Team"))
add_link_data = [{
'text': team_name_for_data_entry(team, use_code_names),
'text': conditional_escape(team_name_for_data_entry(team, use_code_names)),
'link': self.get_from_team_link(team),
} for team in tournament.team_set.all()]
header = {'key': 'team', 'title': _("Team")}
Expand All @@ -372,13 +376,13 @@ def get_tables(self):
'key': 'institution',
'icon': 'home',
'tooltip': _("Institution"),
}, [team.institution.code if team.institution else TabbycatTableBuilder.BLANK_TEXT for team in tournament.team_set.all()])
}, [escape(team.institution.code) if team.institution else TabbycatTableBuilder.BLANK_TEXT for team in tournament.team_set.all()])

adjs_table = TabbycatTableBuilder(view=self, sort_key="adjudicator", title=_("An Adjudicator"))
adjudicators = tournament.adjudicator_set.all()

add_link_data = [{
'text': adj.get_public_name(tournament),
'text': escape(adj.get_public_name(tournament)),
'link': self.get_from_adj_link(adj),
} for adj in adjudicators]
header = {'key': 'adjudicator', 'title': _("Adjudicator")}
Expand All @@ -389,7 +393,7 @@ def get_tables(self):
'key': 'institution',
'icon': 'home',
'tooltip': _("Institution"),
}, [adj.institution.code if adj.institution else TabbycatTableBuilder.BLANK_TEXT for adj in adjudicators])
}, [escape(adj.institution.code) if adj.institution else TabbycatTableBuilder.BLANK_TEXT for adj in adjudicators])

return [teams_table, adjs_table]

Expand Down
3 changes: 2 additions & 1 deletion tabbycat/availability/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from django.db.models import Min
from django.db.models.functions import Coalesce
from django.http import JsonResponse
from django.utils.html import escape
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy, ngettext
from django.views.generic.base import TemplateView, View
Expand Down Expand Up @@ -188,7 +189,7 @@ def get_table(self):
} for inst in queryset])

if self.round.prev:
title = _("Active in %(prev_round)s") % {'prev_round': self.round.prev.abbreviation}
title = _("Active in %(prev_round)s") % {'prev_round': escape(self.round.prev.abbreviation)}
table.add_column({'key': 'active-prev', 'title': title}, [{
'sort': inst.prev_available,
'icon': 'check' if inst.prev_available else '',
Expand Down
9 changes: 5 additions & 4 deletions tabbycat/breakqual/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from django.db.models import Count, Q
from django.forms import HiddenInput
from django.forms.models import BaseModelFormSet
from django.utils.html import escape
from django.utils.translation import gettext as _, ngettext
from django.views.generic import FormView, TemplateView

Expand Down Expand Up @@ -61,7 +62,7 @@ def get_standings(self):

def get_table(self):
self.standings = self.get_standings()
table = TabbycatTableBuilder(view=self, title=self.object.name, sort_key='Rk')
table = TabbycatTableBuilder(view=self, title=escape(self.object.name), sort_key='Rk')
table.add_ranking_columns(self.standings)
table.add_column({'title': _("Break"), 'key': 'break'},
[tsi.break_rank for tsi in self.standings])
Expand Down Expand Up @@ -291,7 +292,7 @@ def get_table(self):

break_categories = t.breakcategory_set.order_by('seq')
for bc in break_categories:
table.add_column({'title': bc.name, 'key': bc.slug}, [{
table.add_column({'title': escape(bc.name), 'key': escape(bc.slug)}, [{
'component': 'check-cell',
'checked': True if bc in team.break_categories.all() else False,
'sort': True if bc in team.break_categories.all() else False,
Expand All @@ -301,13 +302,13 @@ def get_table(self):

# Provide list of members within speaker categories for convenient entry
for sc in speaker_categories:
table.add_column({'title': _('%s Speakers') % sc.name, 'key': sc.name + "_speakers"}, [{
table.add_column({'title': _('%s Speakers') % escape(sc.name), 'key': escape(sc.name) + "_speakers"}, [{
'text': getattr(team, 'nspeakers_%s' % sc.slug, 'N/A'),
'tooltip': ngettext(
'Team has %(nspeakers)s speaker with the %(category)s speaker category assigned',
'Team has %(nspeakers)s speakers with the %(category)s speaker category assigned',
getattr(team, 'nspeakers_%s' % sc.slug, 0),
) % {'nspeakers': getattr(team, 'nspeakers_%s' % sc.slug, 'N/A'), 'category': sc.name},
) % {'nspeakers': getattr(team, 'nspeakers_%s' % sc.slug, 'N/A'), 'category': escape(sc.name)},
} for team in teams])

return table
Expand Down
6 changes: 3 additions & 3 deletions tabbycat/draw/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from django.db.models import OuterRef, Subquery
from django.http import HttpResponseBadRequest, HttpResponseRedirect
from django.utils.functional import cached_property
from django.utils.html import format_html
from django.utils.html import escape, format_html
from django.utils.safestring import mark_safe
from django.utils.timezone import get_current_timezone_name
from django.utils.translation import gettext as _
Expand Down Expand Up @@ -430,7 +430,7 @@ class EmailTeamAssignmentsView(RoundTemplateEmailCreateView):
round_redirect_pattern_name = 'draw-display'

def get_queryset(self):
return Speaker.objects.filter(team__debateteam__round=self.round).select_related('team')
return Speaker.objects.filter(team__debateteam__debate__round=self.round).select_related('team')


# ==============================================================================
Expand Down Expand Up @@ -806,7 +806,7 @@ def get_table(self):
table = TabbycatTableBuilder(view=self)
table.add_team_columns(teams)

headers = [round.abbreviation for round in rounds]
headers = [escape(round.abbreviation) for round in rounds]
data = [[tsas.get((team.id, round.seq), "—") for round in rounds] for team in teams]
table.add_columns(headers, data)

Expand Down
2 changes: 1 addition & 1 deletion tabbycat/notifications/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def standings_email_generator(to, url, round):

for team in teams:
context_team = context.copy()
context_team['POINTS'] = str(team._points)
context_team['POINTS'] = str(team.points_count)
context_team['TEAM'] = team.short_name

for speaker in team.speaker_set.all():
Expand Down
9 changes: 5 additions & 4 deletions tabbycat/notifications/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from django.http import HttpResponse
from django.urls import reverse_lazy
from django.utils import formats, timezone
from django.utils.html import escape
from django.utils.translation import gettext as _, gettext_lazy, ngettext
from django.views.generic.base import View
from django.views.generic.edit import FormView
Expand Down Expand Up @@ -140,8 +141,8 @@ def get_tables(self):
emails_time = []

for sentmessage in notification.sentmessage_set.all():
emails_recipient.append(sentmessage.recipient.name if sentmessage.recipient else self.UNKNOWN_RECIPIENT_CELL)
emails_addresses.append(sentmessage.email or self.UNKNOWN_RECIPIENT_CELL)
emails_recipient.append(escape(sentmessage.recipient.name) if sentmessage.recipient else self.UNKNOWN_RECIPIENT_CELL)
emails_addresses.append(escape(sentmessage.email) or self.UNKNOWN_RECIPIENT_CELL)

if len(sentmessage.statuses) > 0:
latest_status = sentmessage.statuses[0] # already ordered
Expand Down Expand Up @@ -258,12 +259,12 @@ def get_table(self, mixed_participants=False):
} for p in queryset])

table.add_column({'key': 'name', 'tooltip': _("Participant"), 'icon': 'user'}, [{
'text': p.name,
'text': escape(p.name),
'class': 'no-wrap' if len(p.name) < 20 else '',
} for p in queryset])

table.add_column({'key': 'email', 'tooltip': _("Email address"), 'icon': 'mail'}, [{
'text': p.email if p.email else _("Not Provided"),
'text': escape(p.email) if p.email else _("Not Provided"),
'class': 'small' if p.email else 'small text-warning',
} for p in queryset])

Expand Down
6 changes: 2 additions & 4 deletions tabbycat/participants/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,11 +280,9 @@ def points_count(self):
try:
return self._points
except AttributeError:
from results.models import TeamScore
from standings.teams import PointsMetricAnnotator
self._points = TeamScore.objects.filter(
ballot_submission__confirmed=True,
debate_team__team=self,
self._points = self.__class__.objects.filter(
id=self.id,
).aggregate(p=Coalesce(PointsMetricAnnotator().get_annotation(), Value(0)))['p']
return self._points

Expand Down
3 changes: 2 additions & 1 deletion tabbycat/participants/tables.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.db.models import Prefetch
from django.utils.html import escape
from django.utils.translation import gettext as _

from adjallocation.models import DebateAdjudicator
Expand Down Expand Up @@ -34,7 +35,7 @@ def add_cumulative_team_points_column(self, teamscores):
def add_speaker_scores_column(self, teamscores):
data = [{
'text': ", ".join([metricformat(ss.score) for ss in ts.debate_team.speaker_scores]) or "—",
'tooltip': "<br>".join(["%s for %s" % (metricformat(ss.score), ss.speaker) for ss in ts.debate_team.speaker_scores]),
'tooltip': "<br>".join(["%s for %s" % (metricformat(ss.score), escape(ss.speaker)) for ss in ts.debate_team.speaker_scores]),
} for ts in teamscores]
header = {'key': 'speaks', 'tooltip': _("Speaker scores<br>(in speaking order)"), 'text': _("Speaks")}
self.add_column(header, data)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{# Position, teams and room #}
<div class="list-group-item lead {% if draw_released %}active{% else %}list-group-item-dark{% endif %}">

{% person_display_name adjudicator as adjudicator_name %}
{% person_display_name debateadjudicator.adjudicator as adjudicator_name %}

{# (Two-team formats) #}
{% if pref.teams_in_debate == 'two' %}
Expand Down
11 changes: 6 additions & 5 deletions tabbycat/participants/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from django.db.models import Count, Prefetch, Q
from django.forms import HiddenInput
from django.http import JsonResponse
from django.utils.html import escape
from django.utils.translation import gettext as _, gettext_lazy, ngettext
from django.views.generic.base import View

Expand Down Expand Up @@ -99,11 +100,11 @@ def get_table(self):
).distinct()

table = TabbycatTableBuilder(view=self, sort_key='code')
table.add_column({'key': 'code', 'title': _("Code")}, [i.code for i in institutions])
table.add_column({'key': 'name', 'title': _("Full name")}, [i.name for i in institutions])
table.add_column({'key': 'code', 'title': _("Code")}, [escape(i.code) for i in institutions])
table.add_column({'key': 'name', 'title': _("Full name")}, [escape(i.name) for i in institutions])
if any(i.region is not None for i in institutions):
table.add_column({'key': 'region', 'title': _("Region")},
[i.region.name if i.region else "—" for i in institutions])
[escape(i.region.name) if i.region else "—" for i in institutions])
table.add_column({'key': 'nteams', 'title': _("Teams"), 'tooltip': _("Number of teams")},
[i.nteams for i in institutions])
table.add_column({'key': 'nadjs', 'title': _("Adjs"),
Expand Down Expand Up @@ -141,7 +142,7 @@ def get_table(self):
table = TabbycatTableBuilder(view=self, sort_key='code_name')
table.add_column(
{'key': 'code_name', 'title': _("Code name")},
[{'text': t.code_name or "—"} for t in teams],
[{'text': escape(t.code_name) or "—"} for t in teams],
)
table.add_team_columns(teams)
return table
Expand Down Expand Up @@ -380,7 +381,7 @@ def get_table(self):
speaker_categories = self.tournament.speakercategory_set.all()

for sc in speaker_categories:
table.add_column({'key': sc.name, 'title': sc.name}, [{
table.add_column({'key': escape(sc.name), 'title': escape(sc.name)}, [{
'component': 'check-cell',
'checked': True if sc in speaker.categories.all() else False,
'id': speaker.id,
Expand Down
3 changes: 2 additions & 1 deletion tabbycat/results/tables.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django.utils.html import escape
from django.utils.translation import gettext as _

from utils.misc import reverse_tournament
Expand Down Expand Up @@ -63,7 +64,7 @@ def get_ballot_cells(self, debate, tournament, view_role, user):
return {
'component': 'ballots-cell',
'ballots': [b.serialize(tournament) for b in ballotsubmissions],
'current_user': user.username,
'current_user': escape(user.username),
'acting_role': view_role,
'new_ballot': reverse_tournament(new_link, self.tournament,
kwargs={'debate_id': debate.id}),
Expand Down
Loading

0 comments on commit 3e318f8

Please sign in to comment.