Skip to content

Commit c05d22c

Browse files
committed
Merge branch 'hotfix/2.11.1'
2 parents d27e242 + 85526ab commit c05d22c

File tree

34 files changed

+332
-213
lines changed

34 files changed

+332
-213
lines changed

.github/CHANGELOG.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,26 @@
22
Change Log
33
==========
44

5+
2.11.1
6+
------
7+
*Release date: 2 March 2026*
8+
9+
- Avoid merging ballots based on trainee submissions
10+
- Avoid sending password reset email on user invitation
11+
- Remove extraneous HTML when copying into Summernote
12+
- Fix error on motion/info-slide release
13+
- Avoid sending push notifications if not configured and fail silently
14+
- Add validation against setting graph-only pullup option; add error when using draw strength in Australs PP
15+
- Fix adjudicators disappearing from allocation screen in public speaking tournaments
16+
- Only show 'Release info-slide' button if round has info-slide
17+
- Skip teams that are not in standings when calculating draw strength
18+
- Set sides for bye debates when prefetching; skip adding results for bye debates in commands
19+
- Avoid crashing when creating tournaments due to blank required FKs
20+
- Fix duplicate header keys in registration institution table
21+
- Fix JQuery not being loaded in time; fix checkbox table checks not registering; use form-based submission where possible
22+
- Fix typo in preferences. Thank you to Polyxeni Damigou for their PR! (#2822)
23+
24+
525
2.11.0 (Tonkinese)
626
------------------
727
*Release date: 9 November 2025*

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
# The short X.Y version.
6161
version = '2.11'
6262
# The full version, including alpha/beta/rc tags.
63-
release = '2.11.0'
63+
release = '2.11.1'
6464

6565
rst_epilog = """
6666
.. |vrelease| replace:: v{release}

tabbycat/adjallocation/templates/EditDebateAdjudicatorsContainer.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export default {
106106
}),
107107
computed: {
108108
maxTeams: function () {
109-
return Math.max(...this.sortedDebatesOrPanels.map(d => d.teams.length))
109+
return Math.max(...this.sortedDebatesOrPanels.map(d => d.teams ? d.teams.length : 0), 2)
110110
},
111111
groupedDebatesByRound: function () {
112112
// Group already-sorted debates by their round for visual separation

tabbycat/adjfeedback/templates/feedback_overview.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
{% block js %}
6969

7070
<script>
71-
var setBreakingURL = '{% tournamenturl 'adjfeedback-set-adj-breaking-status' %}';
71+
var setBreakingURL = '{% tournamenturl "adjfeedback-set-adj-breaking-status" %}';
7272
</script>
7373
{{ block.super }}
7474
<script>

tabbycat/api/views.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ def get_serializer_context(self):
331331
@extend_schema(summary="Generate break")
332332
def create(self, request, *args, **kwargs):
333333
self.generate_break((self.break_category,))
334-
self.log_action(type=ActionLogEntry.ActionType.BREAK_GENERATE_ONE)
334+
self.log_action(type=ActionLogEntry.ActionType.BREAK_GENERATE_ONE, agent=ActionLogEntry.Agent.API)
335335
return self.list(request, *args, **kwargs)
336336

337337
@extend_schema(summary="Delete break")
@@ -340,7 +340,7 @@ def destroy(self, request, *args, **kwargs):
340340
Destroy is normally for a specific instance, now QuerySet.
341341
"""
342342
self.filter_queryset(self.get_queryset()).delete()
343-
self.log_action(type=ActionLogEntry.ActionType.BREAK_DELETE)
343+
self.log_action(type=ActionLogEntry.ActionType.BREAK_DELETE, agent=ActionLogEntry.Agent.API)
344344
return Response(status=204) # No content
345345

346346
@extend_schema(summary="Update remark and regenerate break")
@@ -1038,7 +1038,7 @@ def get_queryset(self):
10381038
@extend_schema(summary="Delete all pairings in the round")
10391039
def delete_all(self, request, *args, **kwargs):
10401040
self.get_queryset().delete()
1041-
self.log_action(ActionLogEntry.ActionType.DRAW_REGENERATE)
1041+
self.log_action(type=ActionLogEntry.ActionType.DRAW_REGENERATE, agent=ActionLogEntry.Agent.API)
10421042
return Response(status=204) # No content
10431043

10441044

@@ -1158,7 +1158,7 @@ def destroy(self, request, *args, **kwargs):
11581158
instance = self.get_object()
11591159
instance.discarded = True
11601160
instance.save()
1161-
self.log_action(ActionLogEntry.ActionType.BALLOT_DISCARD)
1161+
self.log_action(type=ActionLogEntry.ActionType.BALLOT_DISCARD, agent=ActionLogEntry.Agent.API)
11621162
return self.retrieve(request, *args, **kwargs)
11631163

11641164

@@ -1364,7 +1364,7 @@ def patch(self, request, *args, **kwargs):
13641364

13651365
RoundAvailability.objects.bulk_create(
13661366
[RoundAvailability(content_type=contenttype, round=self.round, object_id=id) for id in ids - existing])
1367-
self.log_action(type=self.action_log_type_updated)
1367+
self.log_action(type=self.action_log_type_updated, agent=ActionLogEntry.Agent.API)
13681368

13691369
return self.get(request, *args, **kwargs)
13701370

@@ -1375,7 +1375,7 @@ def put(self, request, *args, **kwargs):
13751375
contenttype = ContentType.objects.get_for_model(model)
13761376
RoundAvailability.objects.bulk_create(
13771377
[RoundAvailability(content_type=contenttype, round=self.round, object_id=p.id) for p in participants])
1378-
self.log_action(type=self.action_log_type_updated)
1378+
self.log_action(type=self.action_log_type_updated, agent=ActionLogEntry.Agent.API)
13791379
return self.get(request, *args, **kwargs)
13801380

13811381
@extend_schema(summary="Mark objects as unavailable")
@@ -1387,13 +1387,13 @@ def post(self, request, *args, **kwargs):
13871387
content_type=contenttype, round=self.round,
13881388
object_id__in=[p.id for p in participants],
13891389
).delete()
1390-
self.log_action(type=self.action_log_type_updated)
1390+
self.log_action(type=self.action_log_type_updated, agent=ActionLogEntry.Agent.API)
13911391
return self.get(request, *args, **kwargs)
13921392

13931393
@extend_schema(summary="Delete class of availabilities", parameters=extra_params)
13941394
def delete(self, request, *args, **kwargs):
13951395
self.get_queryset().delete()
1396-
self.log_action(type=self.action_log_type_updated)
1396+
self.log_action(type=self.action_log_type_updated, agent=ActionLogEntry.Agent.API)
13971397
return Response(status=204)
13981398

13991399

@@ -1426,7 +1426,7 @@ def get_queryset(self):
14261426
@extend_schema(summary="Delete all preformed panels from round")
14271427
def delete_all(self, request, *args, **kwargs):
14281428
self.get_queryset().delete()
1429-
self.log_action(ActionLogEntry.ActionType.PREFORMED_PANELS_DELETE)
1429+
self.log_action(type=ActionLogEntry.ActionType.PREFORMED_PANELS_DELETE, agent=ActionLogEntry.Agent.API)
14301430
return Response(status=204) # No content
14311431

14321432
@extend_schema(summary="Add blank preformed panels")
@@ -1438,7 +1438,7 @@ def add_blank(self, request, *args, **kwargs):
14381438
'bracket_min': bracket_min,
14391439
'liveness': liveness,
14401440
})
1441-
self.log_action(self.action_log_type_created)
1441+
self.log_action(type=self.action_log_type_created, agent=ActionLogEntry.Agent.API)
14421442

14431443
return self.get(request, *args, **kwargs)
14441444

tabbycat/availability/templates/availability_index.html

Lines changed: 49 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -40,52 +40,55 @@
4040
{% trans "View Draw" %} <i data-feather="chevron-right"></i>
4141
</a>
4242
{% else %}
43-
{% if previous_unconfirmed %}
44-
<button class="btn btn-warning" id="createDraw"
45-
data-toggle="tooltip" title="{% blocktrans trimmed with round=round.prev.name %}{{ previous_unconfirmed }} debates from
46-
{{ round }} do not have a completed ballot — this may lead to a draw that fails or is incorrect{% endblocktrans %}">
47-
{% trans "Generate Draw" %} <i data-feather="chevron-right"></i>
48-
</button>
49-
{% elif availability_info.teams.in_now == 0 %}
50-
<button class="btn btn-danger disabled"
51-
data-toggle="tooltip" title="{% trans "The draw cannot be generated until some teams have been marked as available." %}">
52-
{% trans "Generate Draw" %} <i data-feather="chevron-right"></i>
53-
</button>
54-
{% elif availability_info.adjs.in_now == 0 %}
55-
<button class="btn btn-danger disabled"
56-
data-toggle="tooltip" title="{% trans "The draw cannot be generated until some adjudicators have been marked as available." %}">
57-
{% trans "Generate Draw" %} <i data-feather="chevron-right"></i>
58-
</button>
59-
{% elif availability_info.venues.in_now == 0 %}
60-
<button class="btn btn-danger disabled"
61-
data-toggle="tooltip" title="{% trans "The draw cannot be generated until some rooms have been marked as available." %}">
62-
{% trans "Generate Draw" %} <i data-feather="chevron-right"></i>
63-
</button>
64-
{% elif min_venues > availability_info.venues.in_now %}
65-
<button class="btn btn-warning" id="createDraw"
66-
data-toggle="tooltip" title="{% trans "There aren't enough rooms marked as available for the number of debates — the draw may not generate properly." %}">
67-
{% trans "Generate Draw" %} <i data-feather="chevron-right"></i>
68-
</button>
69-
{% elif min_adjudicators > availability_info.adjs.in_now %}
70-
<button class="btn btn-warning" id="createDraw"
71-
data-toggle="tooltip" title="{% trans "There aren't enough adjudicators marked as available for the number of debates — the draw may not generate properly." %}">
72-
{% trans "Generate Draw" %} <i data-feather="chevron-right"></i>
73-
</button>
74-
{% elif pref.teams_in_debate == 2 and not availability_info.teams.in_now|divisibleby:2 %}
75-
<button class="btn btn-warning" id="createDraw"
76-
data-toggle="tooltip" title="{% trans "There is an uneven number of teams marked as available the draw may not generate properly." %}">
77-
{% trans "Generate Draw" %} <i data-feather="chevron-right"></i>
78-
</button>
79-
{% elif pref.teams_in_debate == 4 and not availability_info.teams.in_now|divisibleby:4 %}
80-
<button class="btn btn-warning" id="createDraw"
81-
data-toggle="tooltip" title="{% trans "The number of teams marked as available is not a multiple of 4 the draw may not generate properly." %}">
82-
{% trans "Generate Draw" %} <i data-feather="chevron-right"></i>
83-
</button>
84-
{% else %}
85-
<button class="btn btn-success" id="createDraw">
86-
{% trans "Generate Draw" %} <i data-feather="chevron-right"></i>
87-
</button>
88-
{% endif %}
43+
<form id="createForm" action="{% roundurl 'draw-create' %}" method="POST">
44+
{% csrf_token %}
45+
{% if previous_unconfirmed %}
46+
<button class="btn btn-warning" id="createDraw"
47+
data-toggle="tooltip" title="{% blocktrans trimmed with round=round.prev.name %}{{ previous_unconfirmed }} debates from
48+
{{ round }} do not have a completed ballot — this may lead to a draw that fails or is incorrect{% endblocktrans %}">
49+
{% trans "Generate Draw" %} <i data-feather="chevron-right"></i>
50+
</button>
51+
{% elif availability_info.teams.in_now == 0 %}
52+
<button class="btn btn-danger disabled"
53+
data-toggle="tooltip" title="{% trans "The draw cannot be generated until some teams have been marked as available." %}">
54+
{% trans "Generate Draw" %} <i data-feather="chevron-right"></i>
55+
</button>
56+
{% elif availability_info.adjs.in_now == 0 %}
57+
<button class="btn btn-danger disabled"
58+
data-toggle="tooltip" title="{% trans "The draw cannot be generated until some adjudicators have been marked as available." %}">
59+
{% trans "Generate Draw" %} <i data-feather="chevron-right"></i>
60+
</button>
61+
{% elif availability_info.venues.in_now == 0 %}
62+
<button class="btn btn-danger disabled"
63+
data-toggle="tooltip" title="{% trans "The draw cannot be generated until some rooms have been marked as available." %}">
64+
{% trans "Generate Draw" %} <i data-feather="chevron-right"></i>
65+
</button>
66+
{% elif min_venues > availability_info.venues.in_now %}
67+
<button class="btn btn-warning" id="createDraw"
68+
data-toggle="tooltip" title="{% trans "There aren't enough rooms marked as available for the number of debates — the draw may not generate properly." %}">
69+
{% trans "Generate Draw" %} <i data-feather="chevron-right"></i>
70+
</button>
71+
{% elif min_adjudicators > availability_info.adjs.in_now %}
72+
<button class="btn btn-warning" id="createDraw"
73+
data-toggle="tooltip" title="{% trans "There aren't enough adjudicators marked as available for the number of debates — the draw may not generate properly." %}">
74+
{% trans "Generate Draw" %} <i data-feather="chevron-right"></i>
75+
</button>
76+
{% elif pref.teams_in_debate == 2 and not availability_info.teams.in_now|divisibleby:2 %}
77+
<button class="btn btn-warning" id="createDraw"
78+
data-toggle="tooltip" title="{% trans "There is an uneven number of teams marked as available the draw may not generate properly." %}">
79+
{% trans "Generate Draw" %} <i data-feather="chevron-right"></i>
80+
</button>
81+
{% elif pref.teams_in_debate == 4 and not availability_info.teams.in_now|divisibleby:4 %}
82+
<button class="btn btn-warning" id="createDraw"
83+
data-toggle="tooltip" title="{% trans "The number of teams marked as available is not a multiple of 4 the draw may not generate properly." %}">
84+
{% trans "Generate Draw" %} <i data-feather="chevron-right"></i>
85+
</button>
86+
{% else %}
87+
<button class="btn btn-success" id="createDraw">
88+
{% trans "Generate Draw" %} <i data-feather="chevron-right"></i>
89+
</button>
90+
{% endif %}
91+
</form>
8992
{% endif %}
9093

9194
{% endblock %}
@@ -225,21 +228,4 @@
225228
{% endfor %}
226229
</div>
227230

228-
<form id="createForm" action="{% roundurl 'draw-create' %}" method="POST">
229-
{% csrf_token %}
230-
</form>
231-
232231
{% endblock content %}
233-
234-
{% block js %}
235-
{{ block.super }}
236-
<script>
237-
$(document).ready( function() {
238-
$("#createDraw").click(function(event) {
239-
$.fn.loadButton(event.target); // Prevent double submission
240-
$("#createForm").submit();
241-
return false;
242-
});
243-
});
244-
</script>
245-
{% endblock js %}

tabbycat/checkins/templates/checkin_ids.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ <h5 class="card-title mb-0">{{ check_info.title }}</h5>
3737
{% else %}
3838
{% trans "Generate all identifiers" as text %}
3939
{% endif %}
40-
<div class="list-item-group">
40+
<div class="list-group-item">
4141
<form action="{% tournamenturl 'admin-checkin-generate' kind=check_key %}" method="POST">
4242
{% csrf_token %}
4343
<button type="submit" class="btn btn-primary btn-block">{{ text }}</button>

tabbycat/draw/generator/powerpair.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,11 @@ def room_rank_ordering(self, p):
358358

359359
class AustralsPairingMixin:
360360

361+
def resolve_odd_brackets(self, brackets):
362+
if self.options['odd_bracket'] in ('pullup_lowest_ds_rank', 'pullup_lowest_ds_rank_npulls'):
363+
raise DrawUserError(_("Draw strength pullups require 'Minimum cost matching (including pullups)' as the conflict avoidance method"))
364+
return super().resolve_odd_brackets(brackets)
365+
361366
def generate_pairings(self, brackets):
362367
"""Returns a function taking an OrderedDict as returned by
363368
resolve_odd_brackets(), and returning a list of Debates."""
@@ -571,7 +576,7 @@ def _pullup_random(team, size=None):
571576

572577
@staticmethod
573578
def _pullup_lowest_ds_rank(team, size=None):
574-
return -team.draw_strength_rank
579+
return -getattr(team, 'draw_strength_rank', 0)
575580

576581
@staticmethod
577582
def _pullup_lowest_ds_rank_npulls(team, size=None):

0 commit comments

Comments
 (0)