Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ drf-spectacular = "*"
daphne = "*"
drf-link-header-pagination = "*"
networkx = "*" # Avoid its dependencies (SciPy)
stripe = "*"
requests = "*"
cryptography = "*"

[dev-packages]
pre-commit = "*"
Expand Down
1,758 changes: 957 additions & 801 deletions Pipfile.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion tabbycat/adjfeedback/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from adjallocation.models import DebateAdjudicator
from registration.models import Answer, Question
from results.models import Submission
from results.submission_model import Submission
from utils.models import UniqueConstraint


Expand Down
54 changes: 54 additions & 0 deletions tabbycat/options/preferences.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from dynamic_preferences.types import (BooleanPreference, ChoicePreference, DecimalPreference, FloatPreference,
IntegerPreference, LongStringPreference, MultipleChoicePreference, StringPreference)

from registration.types import Currency
from standings.speakers import SpeakerStandingsGenerator
from standings.teams import TeamStandingsGenerator
from tournaments.utils import get_side_name_choices
Expand Down Expand Up @@ -1765,3 +1766,56 @@ class TeamRegisterMessage(LongStringPreference):
default = ""
widget = SummernoteWidget(attrs={'height': 150, 'class': 'form-summernote'})
field_kwargs = {'required': False}


@tournament_preferences_registry.register
class MissingAdjudicatorRule(ChoicePreference):
help_text = _("If charging a missing adjudicator fee per institution, how to determine the fee")
verbose_name = _("Missing adjudicator fee rule")
section = registration
name = 'missing_adj_rule'
choices = (
('', _("None")),
('N', _("N=N (Equal number of teams and adjudicators)")),
('N-1', _("N-1 (One fewer adjudicators required than teams)")),
('N/2', _("N/2 (Require half the adjudicators to the number of teams, rounded down)")),
)
default = ''
functions = {
'': lambda a, t: 0,
'N': lambda a, t: t - a,
'N-1': lambda a, t: t - a - 1,
'N/2': lambda a, t: t // 2 - a,
}


@tournament_preferences_registry.register
class BillingStandard(ChoicePreference):
help_text = _("How should institutions be billed for their participants")
verbose_name = _("Institutional billing standard")
section = registration
name = 'inst_billing_standard'
choices = (
('active_participants', _("Bill based on the number of participants registered")),
('allocated', _("Bill based on the number of participants allocated")),
)
default = 'active_participants'


@tournament_preferences_registry.register
class BillingCurrency(ChoicePreference):
help_text = _("In which currency should items be charged?")
verbose_name = _("Billing currency")
section = registration
name = 'billing_currency'
choices = Currency.choices
default = Currency.cad


@tournament_preferences_registry.register
class AllowPartialPayments(BooleanPreference):
help_text = _("Whether people paying an invoice can input how much they'll pay at a time")
verbose_name = _("Allow partial payments")
section = registration
name = 'partial_payments'
default = False
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='person',
name='last_name',
field=models.CharField(help_text='The last name may be used for team names or code names', max_length=70, null=True, verbose_name='last name'),
field=models.CharField(blank=True, help_text='The last name may be used for team names or code names', max_length=70, null=True, verbose_name='last name'),
),
migrations.AlterField(
model_name='tournamentinstitution',
Expand Down
58 changes: 58 additions & 0 deletions tabbycat/registration/forms.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import random
from math import log10

from django import forms
from django.utils.text import capfirst
from django.utils.translation import gettext_lazy as _

from options.preferences import MissingAdjudicatorRule
from participants.emoji import EMOJI_RANDOM_FIELD_CHOICES, pick_unused_emoji
from participants.models import Adjudicator, Coach, Institution, Speaker, Team, TournamentInstitution
from privateurls.utils import populate_url_keys

from .form_utils import CustomQuestionsFormMixin
from .models import LineItem, PriceItem
from .payment_utils import add_item


class TournamentInstitutionForm(CustomQuestionsFormMixin, forms.ModelForm):
Expand Down Expand Up @@ -260,8 +264,62 @@ def _create_and_initialise_fields(self):

def save(self):
qs = self.tournament.tournamentinstitution_set.select_related('institution').all()

items = self.tournament.priceitem_set.filter(active=True, item_type__in=[
PriceItem.ItemType.PARTICIPANT, PriceItem.ItemType.TEAM, PriceItem.ItemType.SPEAKER,
PriceItem.ItemType.ADJUDICATOR, PriceItem.ItemType.MISSING_ADJ,
])
new_cart_items = []
for t_inst in qs:
institution = t_inst.institution

if self.tournament.pref('inst_billing_standard') == 'allocated':
n_adjs = self.cleaned_data[self._fieldname_adjs_allocated(institution)] - t_inst.adjudicators_allocated
n_teams = self.cleaned_data[self._fieldname_teams_allocated(institution)] - t_inst.teams_allocated

missing_adj_rule = MissingAdjudicatorRule.functions[self.tournament.pref('missing_adj_rule')]
before_missing = max(missing_adj_rule(t_inst.adjudicators_allocated, t_inst.teams_allocated), 0)
after_missing = missing_adj_rule(self.cleaned_data[self._fieldname_adjs_allocated(institution)], self.cleaned_data[self._fieldname_teams_allocated(institution)])
new_missing = max(after_missing - before_missing, 0)

cart_items = [
ci for ci in
[
add_item(items, PriceItem.ItemType.ADJUDICATOR, n_adjs),
add_item(items, PriceItem.ItemType.TEAM, n_teams),
add_item(items, PriceItem.ItemType.SPEAKER, n_teams * self.tournament.pref('speakers_in_team')),
add_item(items, PriceItem.ItemType.PARTICIPANT, n_adjs + n_teams * self.tournament.pref('speakers_in_team')),
add_item(items, PriceItem.ItemType.MISSING_ADJ, new_missing),
]
if ci is not None and ci.quantity
]
for ci in cart_items:
ci.institution = institution
new_cart_items.extend(cart_items)

t_inst.teams_allocated = self.cleaned_data[self._fieldname_teams_allocated(institution)]
t_inst.adjudicators_allocated = self.cleaned_data[self._fieldname_adjs_allocated(institution)]
TournamentInstitution.objects.bulk_update(qs, ['teams_allocated', 'adjudicators_allocated'])
LineItem.objects.bulk_create(new_cart_items)


class PriceItemForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk:
self.fields['item_type'].disabled = True


class CreatePaymentForm(forms.Form):
def __init__(self, currency, max_value, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['amount'] = forms.DecimalField(min_value=0, max_value=max_value, decimal_places=int(log10(currency.minor_unit)))


class FinishPayPalIntegrationForm(forms.Form):
tracking_id = forms.CharField(widget=forms.HiddenInput(), required=True)
merchant_id = forms.CharField(widget=forms.HiddenInput(), required=True)


class PayPalCaptureOrderForm(forms.Form):
order_id = forms.CharField()
Loading
Loading