Repository: TabbycatDebate/tabbycat

Issue:
Allow dedicated reply speakers (w/o substantives)
Although this is rare, some World Schools-style tournaments (especially smaller/internal ones) allow the fourths member of a team to act as the reply speaker. This is currently not allowed in Tabbycat (tested in `master`, but the code looks the same in `develop`) as [a check in forms.py](https://github.com/TabbycatDebate/tabbycat/blob/develop/tabbycat/results/forms.py#L508) throws a validation error in that case ("The reply speaker for this team did not give a substantive speech").

As I'm tabbing a tournament with this rule in the near future, I have [disabled that check](https://github.com/franga2000/tabbycat/blob/horrible-hack-master/tabbycat/results/forms.py#L508) in my fork and it seems to be working fine.

My suggestion is to add a tournament-level setting to toggle this behavior. I still agree not permitting it should be the default, but I don't see any harm in giving users the option.

I can contribute the feature if you'd accept it since I have plenty of Django experience, but I'm not sure how your licensing works with external contributions. What do you suggest?


Discussion:
Sounds good! We're currently refactoring the results backend (see #1180), but the check is relatively unchanged. To add a preference, you can create a subclass of `BooleanPreference` in `options/preferences.py` (I'd add it to "Debate Rules"). Then, the condition can be found with `self.tournament.pref('...')`.

We'd appreciate the change! It's also true that our current licensing scheme is somewhat undefined for contributors, we try to encourage contributions and we're in the process of changing to a true open-source license (#1028)
Great to hear you're making it fully open source! 

I've implemented the setting, I just need some feedback on the wording to see if it makes enough sense:
![image](https://user-images.githubusercontent.com/3891092/89577295-c4ccb000-d830-11ea-8941-67cd4355f7da.png).

If it does, I can send a PR immediately.
Sounds good, but asking myself whether I would understand the title just by scrolling on the page. In that, we could maybe be more explicit, like "Require reply speaker to have given a substantive speech", then maybe make it plural like with other preferences and the description (only suggestion would be to remove "only" as redundant). Looks good!
> As I'm tabbing a tournament with this rule in the near future, I have disabled that check in my fork and it seems to be working fine.

Yeah, this makes sense. The check is only enforced on saving the form; it's just intended to prevent human error. The database doesn't care, as you'll have noticed, so just making the check optional sounds fine. Thanks, @franga2000!
Hi @franga2000 just following up on this, were you still keen to contribute that PR? I think Etienne's wording of "Require reply speaker to have given a substantive speech" works well.
Of course, I just need to get back to my main computer as I've been away from home the past week. I just need to change the wording and rebase everything on top of `develop` (I made the change the change on `master` in order to use it at my tournament). 

I'll try to get it in by Monday.
No rush, was just checking in! 

Resolving patch:
diff --git a/tabbycat/options/preferences.py b/tabbycat/options/preferences.py
--- a/tabbycat/options/preferences.py
+++ b/tabbycat/options/preferences.py
@@ -493,6 +493,15 @@ class ReplyScores(BooleanPreference):
     default = True
 
 
+@tournament_preferences_registry.register
+class RequireSubstantiveForReply(BooleanPreference):
+    help_text = _("Whether to limit reply speeches to speakers who gave a constructive speech in the debate")
+    verbose_name = _("Require reply speaker to have given a substantive speech")
+    section = debate_rules
+    name = 'require_substantive_for_reply'
+    default = True
+
+
 @tournament_preferences_registry.register
 class MotionVetoes(BooleanPreference):
     help_text = _("Enables the motion veto field on ballots, to track veto statistics")
diff --git a/tabbycat/results/forms.py b/tabbycat/results/forms.py
--- a/tabbycat/results/forms.py
+++ b/tabbycat/results/forms.py
@@ -507,7 +507,7 @@ def clean_speakers(self, cleaned_data):
                     ))
 
                 # The reply speaker must have given a substantive speech.
-                if len(speaker_positions.get(reply_speaker, [])) == 0:
+                if self.tournament.pref('require_substantive_for_reply') and len(speaker_positions.get(reply_speaker, [])) == 0:
                     self.add_error(self._fieldname_speaker(side, self.reply_position), forms.ValidationError(
                         _("The reply speaker for this team did not give a substantive speech."),
                         code='reply_speaker_not_repeat',


Test patch: