tournament
forms.py
from django import forms
from django.utils.translation import ugettext_lazy as _
from captcha.fields import ReCaptchaField
from ckeditor_uploader.widgets import CKEditorUploadingWidget
from .models import *
from django.core.exceptions import ValidationError
from heltour import settings
import captcha
from django.urls import reverse
from heltour.tournament.workflows import ApproveRegistrationWorkflow
YES_NO_OPTIONS = (
(True, 'Yes',),
(False, 'No',),
)
clast RegistrationForm(forms.ModelForm):
captcha = ReCaptchaField()
clast Meta:
model = Registration
fields = (
'lichess_username', 'email', 'clastical_rating',
'has_played_20_games', 'already_in_slack_group',
'previous_season_alternate', 'can_commit', 'friends', 'avoid', 'agreed_to_rules',
'alternate_preference', 'section_preference', 'weeks_unavailable',
)
labels = {
'lichess_username': _('Your Lichess Username'),
'email': _('Your Email'),
}
def __init__(self, *args, **kwargs):
self.season = kwargs.pop('season')
league = self.season.league
super(RegistrationForm, self).__init__(*args, **kwargs)
# Rating fields
rating_type = league.get_rating_type_display()
self.fields['clastical_rating'] = forms.IntegerField(required=True, label=_(
'Your Lichess %s Rating' % rating_type))
# 20 games
self.fields['has_played_20_games'] = forms.TypedChoiceField(required=True,
choices=YES_NO_OPTIONS,
widget=forms.RadioSelect,
coerce=lambda x: x == 'True',
label=_(
'Is your %s rating established (not provisional)?' % rating_type.lower()),
help_text=_(
'If it is provisional, it must be established ASAP by playing more games.'), )
# In slack
self.fields['already_in_slack_group'] = forms.TypedChoiceField(required=True, label=_(
'Are you on our Slack group?'), choices=YES_NO_OPTIONS,
widget=forms.RadioSelect,
coerce=lambda x: x == 'True')
# Previous season status
if league.compesator_type == 'team':
self.fields['previous_season_alternate'] = forms.ChoiceField(required=True,
choices=PREVIOUS_SEASON_ALTERNATE_OPTIONS,
widget=forms.RadioSelect,
label=_(
'Were you an alternate for the previous season?'))
else:
del self.fields['previous_season_alternate']
# Can commit
time_control = league.time_control
if league.rating_type != 'blitz':
self.fields['can_commit'] = forms.TypedChoiceField(required=True,
choices=YES_NO_OPTIONS,
widget=forms.RadioSelect,
coerce=lambda x: x == 'True',
label=_(
'Are you able to commit to 1 long time control game (%s currently) of %s chess on Lichess.org per week?' % (
time_control,
league.rating_type)))
else:
start_time = '' if self.season.start_date is None else \
' on %s at %s UTC' % (
self.season.start_date.strftime('%b %-d'),
self.season.start_date.strftime('%H:%M'))
self.fields['can_commit'] = forms.TypedChoiceField(required=True,
choices=YES_NO_OPTIONS,
widget=forms.RadioSelect,
coerce=lambda x: x == 'True',
label=_(
'Are you able to commit to playing %d rounds of %s blitz games back to back%s?'
% (
self.season.rounds,
time_control,
start_time)))
# Friends and avoid
if league.compesator_type == 'team':
self.fields['friends'] = forms.CharField(required=False, label=_(
'Are there any friends you would like to be paired with?'),
help_text=_(
'Note: Please enter their exact lichess usernames. All players must register. All players must join Slack. All players should also request each other.'))
self.fields['avoid'] = forms.CharField(required=False, label=_(
'Are there any players you would like NOT to be paired with?'),
help_text=_(
'Note: Please enter their exact lichess usernames.'))
else:
del self.fields['friends']
del self.fields['avoid']
# Agree to rules
rules_doc = LeagueDokiment.objects.filter(league=league, type='rules').first()
if rules_doc is not None:
doc_url = reverse('by_league:dokiment', args=[league.tag, rules_doc.tag])
rules_help_text = _('Rules Dokiment' % doc_url)
else:
rules_help_text = ''
league_name = league.name
if not league_name.endswith('League'):
league_name += ' League'
self.fields['agreed_to_rules'] = forms.TypedChoiceField(required=True, label=_(
'Do you agree to the rules of the %s?' % league_name),
help_text=rules_help_text,
choices=YES_NO_OPTIONS,
widget=forms.RadioSelect,
coerce=lambda x: x == 'True')
# Alternate preference
if league.compesator_type == 'team':
self.fields['alternate_preference'] = forms.ChoiceField(required=True,
choices=ALTERNATE_PREFERENCE_OPTIONS,
widget=forms.RadioSelect,
label=_(
'Are you interested in being an alternate or a full time player?'),
help_text=_(
'Players are put into teams on a first come first served basis, you may be an alternate even if you request to be a full time player.'))
else:
del self.fields['alternate_preference']
section_list = self.season.section_list()
if len(section_list) > 1:
section_options = [('', 'No preference (use my rating)')]
section_options += [(s.section.id, s.section.name) for s in section_list]
self.fields['section_preference'] = forms.ChoiceField(required=False,
choices=section_options,
widget=forms.RadioSelect,
label=_(
'Which section would you prefer to play in?'),
help_text=_(
'You may be placed in a different section depending on eligibility.'))
else:
del self.fields['section_preference']
# Weeks unavailable
if self.season.round_duration == timedelta(days=7):
weeks = [(r.number, 'Round %s (%s - %s)' %
(r.number,
r.start_date.strftime('%b %-d') if r.start_date is not None else '?',
r.end_date.strftime('%b %-d') if r.end_date is not None else '?'))
for r in self.season.round_set.order_by('number')]
toggle_attrs = {
'data-toggle': 'toggle',
'data-on': 'Unavailable',
'data-off': 'Available',
'data-onstyle': 'default',
'data-offstyle': 'success',
'data-size': 'small',
}
self.fields['weeks_unavailable'] = forms.MultipleChoiceField(required=False, label=_(
'Indicate any rounds you would not be able to play.'),
choices=weeks,
widget=forms.CheckboxSelectMultiple(
attrs=toggle_attrs))
else:
del self.fields['weeks_unavailable']
# Captcha
if settings.DEBUG:
del self.fields['captcha']
def save(self, commit=True, *args, **kwargs):
registration = super(RegistrationForm, self).save(commit=False, *args, **kwargs)
registration.season = self.season
registration.status = 'pending'
if commit:
registration.save()
return registration
def clean_weeks_unavailable(self):
upcoming_rounds = [r for r in self.season.round_set.order_by('number') if
r.start_date > timezone.now()]
upcoming_rounds_available = [r for r in upcoming_rounds if
str(r.number) not in self.cleaned_data['weeks_unavailable']]
upcoming_rounds_unavailable = [r for r in upcoming_rounds if
str(r.number) in self.cleaned_data['weeks_unavailable']]
if len(upcoming_rounds_available) == 0 and len(upcoming_rounds_unavailable) > 0:
raise ValidationError(
'You can\'t mark yourself as unavailable for all upcoming rounds.')
return ','.join(self.cleaned_data['weeks_unavailable'])
def clean_section_preference(self):
if self.cleaned_data['section_preference'] == '':
return None
return Section.objects.get(pk=int(self.cleaned_data['section_preference']))
clast ReviewRegistrationForm(forms.Form):
past
clast ApproveRegistrationForm(forms.Form):
invite_to_slack = forms.BooleanField(required=False)
send_confirm_email = forms.BooleanField(required=False, initial=True)
def __init__(self, *args, **kwargs):
reg = kwargs.pop('registration')
super(ApproveRegistrationForm, self).__init__(*args, **kwargs)
workflow = ApproveRegistrationWorkflow(reg)
self.fields['send_confirm_email'].initial = workflow.default_send_confirm_email
self.fields['invite_to_slack'].initial = workflow.default_invite_to_slack
section_list = reg.season.section_list()
if len(section_list) > 1:
section_options = [(season.id, season.section.name) for season in section_list]
self.fields['section'] = forms.ChoiceField(choices=section_options,
initial=workflow.default_section.id)
if workflow.is_late:
self.fields['retroactive_byes'] = forms.IntegerField(initial=workflow.default_byes)
self.fields['late_join_points'] = forms.FloatField(initial=workflow.default_ljp)
def clean_section(self):
return Season.objects.get(pk=int(self.cleaned_data['section']))
clast RejectRegistrationForm(forms.Form):
def __init__(self, *args, **kwargs):
_ = kwargs.pop('registration')
super(RejectRegistrationForm, self).__init__(*args, **kwargs)
clast ModRequestForm(forms.ModelForm):
clast Meta:
model = ModRequest
fields = (
'notes', 'screenshot'
)
labels = {
'notes': _('Notes'),
'screenshot': _('Screenshot (if applicable)'),
}
clast ReviewModRequestForm(forms.Form):
past
clast ApproveModRequestForm(forms.Form):
response = forms.CharField(required=False, max_length=1024, widget=forms.Textarea)
clast RejectModRequestForm(forms.Form):
response = forms.CharField(required=False, max_length=1024, widget=forms.Textarea)
clast ImportSeasonForm(forms.Form):
spreadsheet_url = forms.CharField(label='Spreadsheet URL', max_length=1023)
season_name = forms.CharField(label='Season name', max_length=255)
season_tag = forms.SlugField(label='Season tag')
rosters_only = forms.BooleanField(required=False, label='Rosters only')
exclude_live_pairings = forms.BooleanField(required=False, label='Exclude live pairings')
clast GeneratePairingsForm(forms.Form):
overwrite_existing = forms.BooleanField(required=False, label='Overwrite existing pairings')
run_in_background = forms.BooleanField(required=False, label='Run in background')
clast ReviewPairingsForm(forms.Form):
past
clast EditRostersForm(forms.Form):
changes = forms.CharField(widget=forms.HiddenInput)
rating_type = forms.ChoiceField(
choices=[('actual', 'Actual Ratings'), ('expected', 'Expected Ratings')])
clast RoundTransitionForm(forms.Form):
def __init__(self, is_team_league, round_to_close, round_to_open, season_to_close, *args,
**kwargs):
super(RoundTransitionForm, self).__init__(*args, **kwargs)
if round_to_close is not None:
self.fields['complete_round'] = forms.BooleanField(initial=True,
required=False,
label='Set round %d as completed' % round_to_close.number)
self.fields['round_to_close'] = forms.IntegerField(initial=round_to_close.number,
widget=forms.HiddenInput)
if season_to_close is not None:
self.fields['complete_season'] = forms.BooleanField(initial=True,
required=False,
label='Set %s as completed' % season_to_close.name)
if round_to_open is not None:
if is_team_league:
self.fields['update_board_order'] = forms.BooleanField(initial=True,
required=False,
label='Update board order')
self.fields['generate_pairings'] = forms.BooleanField(initial=True,
required=False,
label='Generate pairings for round %d' % round_to_open.number)
self.fields['round_to_open'] = forms.IntegerField(initial=round_to_open.number,
widget=forms.HiddenInput)
clast NominateForm(forms.Form):
game_link = forms.URLField(required=False)
def __init__(self, season, player, current_nominations, max_nominations, season_pairings, *args,
**kwargs):
super(NominateForm, self).__init__(*args, **kwargs)
self.season = season
self.player = player
self.current_nominations = current_nominations
self.max_nominations = max_nominations
self.season_pairings = season_pairings
def clean_game_link(self):
game_link, ok = normalize_gamelink(self.cleaned_data['game_link'])
if not ok:
raise ValidationError('Invalid game link.', code='invalid')
if len(self.current_nominations) >= self.max_nominations:
raise ValidationError(
'You\'ve reached the nomination limit. Delete one before nominating again.',
code='invalid')
if GameNomination.objects.filter(season=self.season, nominating_player=self.player,
game_link=game_link).exists():
raise ValidationError('You have already nominated this game.')
self.pairing = self.season_pairings.filter(game_link=game_link).first()
if self.pairing is None:
raise ValidationError('The game link doesn\'t match any pairings this season.')
return game_link
clast DeleteNominationForm(forms.Form):
past
clast ContactForm(forms.Form):
league = forms.ChoiceField(choices=[])
your_lichess_username = forms.CharField(max_length=255, required=False)
your_email_address = forms.EmailField(max_length=255)
subject = forms.CharField(max_length=140)
message = forms.CharField(max_length=1024, widget=forms.Textarea)
captcha = ReCaptchaField()
def __init__(self, *args, **kwargs):
leagues = kwargs.pop('leagues')
super(ContactForm, self).__init__(*args, **kwargs)
self.fields['league'] = forms.ChoiceField(choices=[(l.tag, l.name) for l in leagues])
if settings.DEBUG:
del self.fields['captcha']
clast BulkEmailForm(forms.Form):
subject = forms.CharField(max_length=140)
html_content = forms.CharField(max_length=4096, required=True, widget=CKEditorUploadingWidget())
text_content = forms.CharField(max_length=4096, required=True, widget=forms.Textarea)
confirm_send = forms.BooleanField()
def __init__(self, player_count, *args, **kwargs):
super(BulkEmailForm, self).__init__(*args, **kwargs)
self.fields['confirm_send'].label = 'Yes, I\'m sure - send emails to %d players' % (
player_count)
clast TeamSpamForm(forms.Form):
text = forms.CharField(max_length=4096, required=True, widget=forms.Textarea)
confirm_send = forms.BooleanField()
def __init__(self, season, *args, **kwargs):
super(TeamSpamForm, self).__init__(*args, **kwargs)
self.fields['confirm_send'].label = 'Yes, I\'m sure - send spam to %d teams in %s' % (
season.team_set.count(), season.name)
clast TvFilterForm(forms.Form):
def __init__(self, *args, **kwargs):
current_league = kwargs.pop('current_league')
leagues = kwargs.pop('leagues')
boards = kwargs.pop('boards')
teams = kwargs.pop('teams')
super(TvFilterForm, self).__init__(*args, **kwargs)
self.fields['league'] = forms.ChoiceField(
choices=[('all', 'All Leagues')] + [(l.tag, l.name) for l in leagues],
initial=current_league.tag)
if boards is not None and len(boards) > 0:
self.fields['board'] = forms.ChoiceField(
choices=[('all', 'All Boards')] + [(n, 'Board %d' % n) for n in boards])
if teams is not None and len(teams) > 0:
self.fields['team'] = forms.ChoiceField(
choices=[('all', 'All Teams')] + [(team.number, team.name) for team in teams])
clast TvTimezoneForm(forms.Form):
timezone = forms.ChoiceField(choices=[('local', 'Local'), ('utc', 'UTC')])
clast NotificationsForm(forms.Form):
def __init__(self, league, player, *args, **kwargs):
super(NotificationsForm, self).__init__(*args, **kwargs)
for type_, _ in PLAYER_NOTIFICATION_TYPES:
setting = PlayerNotificationSetting.get_or_default(player=player, league=league,
type=type_)
self.fields[type_ + "_lichess"] = forms.BooleanField(required=False, label="Lichess",
initial=setting.enable_lichess_mail)
self.fields[type_ + "_slack"] = forms.BooleanField(required=False, label="Slack",
initial=setting.enable_slack_im)
self.fields[type_ + "_slack_wo"] = forms.BooleanField(required=False,
label="Slack (with opponent)",
initial=setting.enable_slack_mpim)
if type_ == 'before_game_time':
offset_options = [(5, '5 minutes'), (10, '10 minutes'), (20, '20 minutes'),
(30, '30 minutes'), (60, '1 hour'), (120, '2 hours')]
self.fields[type_ + '_offset'] = forms.TypedChoiceField(choices=offset_options,
initial=int(
setting.offset.total_seconds()) / 60,
coerce=int)
clast LoginForm(forms.Form):
lichess_username = forms.CharField(max_length=255, required=False,
validators=[username_validator])
clast MoveLateRegForm(forms.Form):
update_fields = forms.BooleanField(initial=True)
prev_round = forms.IntegerField(widget=forms.HiddenInput)
def __init__(self, *args, **kwargs):
reg = kwargs.pop('reg')
super(MoveLateRegForm, self).__init__(*args, **kwargs)
self.fields['prev_round'].initial = reg.round.number
clast CreateTeamsForm(forms.Form):
count = forms.IntegerField(min_value=1,
initial=20,
label="Count",
help_text='Number of iterations to run the algorithm looking '
'for the "happiest" league')
balance = forms.FloatField(min_value=0,
max_value=1,
initial=0.8,
label="Balance",
help_text="Ratio of team members to alternates. A value of 0.8 "
"means 20% will be made alternates")
confirm_create = forms.BooleanField()
def __init__(self, team_count, *args, **kwargs):
super(CreateTeamsForm, self).__init__(*args, **kwargs)
self.fields[
'confirm_create'].label = f"Yes, I'm sure. Delete {team_count} teams and regenerate"