class TransferForm(forms.Form): submit_label = 'Transfer' target_enrolment = forms.ModelChoiceField( queryset=Enrolment.objects.all(), label='Transfer to', ) amount = forms.DecimalField( min_value=0, error_messages={'min_value': 'Amount must be positive'}, widget=PoundInput(), ) narrative = forms.CharField() type = forms.ModelChoiceField(queryset=models.TransactionType.objects.filter(is_cash=True, is_active=True)) def __init__(self, source_enrolment: Enrolment, *args, **kwargs): super().__init__(*args, **kwargs) # Limit target field to the student's other enrolments self.fields['target_enrolment'].queryset = ( Enrolment.objects.filter(qa__student=source_enrolment.qa.student) .exclude(id=source_enrolment.id) .order_by('-module__start_date') .select_related('module') ) # Easier than subclassing ModelChoiceField solely for this self.fields['target_enrolment'].label_from_instance = lambda obj: f'{obj.module} ({obj.module.code})'
class MultipleEnrolmentPaymentForm(forms.Form): """Allows distributing a single payment between multiple enrolments. Creates a field for every enrolment provided. Returns an `allocations` dict as part of `cleaned_data` for easy iteration over the results """ submit_label = 'Add' amount = forms.DecimalField(widget=PoundInput(), label='Total paid') narrative = forms.CharField() type = forms.ModelChoiceField(queryset=models.TransactionType.objects.filter(is_cash=True, is_active=True)) def clean(self): # Extract the allocation fields, and create a dictionary of {id: amount} for easy processing allocations: dict[int, Decimal] = { int(key.replace('allocation_', '')): val for key, val in self.cleaned_data.items() if 'allocation_' in key } self.cleaned_data['allocations'] = allocations # Ensure the total allocations matches the amount amount = self.cleaned_data.get('amount') allocated = sum(allocations.values()) if amount and allocated != amount: self.add_error('amount', f'Does not match amount allocated below (£{allocated})') def __init__(self, enrolments: list[Enrolment], *args, **kwargs): super().__init__(*args, **kwargs) # Add an input row for every enrolment for enrolment in enrolments: fieldname = f'allocation_{enrolment.id}' self.fields[fieldname] = forms.DecimalField( label=f'{enrolment.module.code}', help_text=f'£{enrolment.get_balance():.2f} owing • {enrolment.module.title}', widget=PoundInput(), )
class Meta: model = models.PaymentPlan fields = ('invoice', 'type', 'status', 'amount') help_texts = { 'status': "If plan already exists, choose 'Payment schedule active'" } widgets = {'amount': PoundInput()}
def __init__(self, enrolments: list[Enrolment], *args, **kwargs): super().__init__(*args, **kwargs) # Add an input row for every enrolment for enrolment in enrolments: fieldname = f'allocation_{enrolment.id}' self.fields[fieldname] = forms.DecimalField( label=f'{enrolment.module.code}', help_text=f'£{enrolment.get_balance():.2f} owing • {enrolment.module.title}', widget=PoundInput(), )
class Meta: model = Fee fields = ( 'amount', 'type', 'description', 'allocation', 'eu_fee', 'is_visible', 'is_payable', 'is_catering', 'is_single_accom', 'is_twin_accom', 'credit_fee', 'end_date', 'limit', ) widgets = {'amount': PoundInput()}
class WeeklyTeachingForm(forms.Form): rate = forms.DecimalField(max_digits=10, decimal_places=4, widget=PoundInput(), disabled=True, label='Hourly rate') length = forms.DecimalField( max_digits=10, decimal_places=1, validators=[validators.MinValueValidator(0), validators.MaxValueValidator(100)], label='Hours per week (prep & teaching)', help_text='Typically 4 for in-person, 5 for online', ) no_meetings = forms.IntegerField( validators=[validators.MinValueValidator(1), validators.MaxValueValidator(21)], label='Number of meetings', ) schedule = forms.TypedChoiceField(choices=schedule_choices, coerce=int) approver = ApproverChoiceField('tutor_payment.approve') def clean(self) -> None: self.cleaned_data['schedule'] = models.schedules[self.cleaned_data['schedule']]
class Meta: first_fields, last_fields = [ 'enrolment', 'type', 'status', 'amount', 'reason' ], [ 'details', 'approver', 'batch', 'narrative', 'is_complete', ] model = models.Amendment fields = first_fields + last_fields widgets = { 'amount': PoundInput(), 'enrolment': forms.HiddenInput(), 'type': ReadOnlyModelWidget(model=models.AmendmentType), 'status': ReadOnlyModelWidget(model=models.AmendmentStatus), }
class CreditForm(forms.Form): submit_label = 'Add credit' enrolment = forms.ModelChoiceField(queryset=Enrolment.objects.all()) account = fields_for_model(Ledger)['account'] amount = forms.DecimalField( widget=PoundInput(), help_text='Positive values decrease debt, negative increase debt') narrative = forms.CharField() type = forms.ModelChoiceField( queryset=TransactionType.objects.filter(is_cash=False, is_active=True)) def __init__(self, invoice: models.Invoice, *args, **kwargs): super().__init__(*args, **kwargs) # Limit the enrolments to those connected with the invoice self.fields['enrolment'].queryset = (Enrolment.objects.filter( ledger__invoice=invoice).distinct().select_related( 'module').order_by('-id')) # Dropdown options display the module title self.fields['enrolment'].label_from_instance = lambda obj: obj.module
class PaymentForm(forms.Form): submit_label = 'Add payment' enrolment = forms.ModelChoiceField( queryset=Enrolment.objects.all(), required=False, empty_label=' – All – ', ) amount = forms.DecimalField(widget=PoundInput()) narrative = forms.CharField() type = forms.ModelChoiceField( queryset=TransactionType.objects.filter(is_cash=True, is_active=True)) def __init__(self, invoice: models.Invoice, *args, **kwargs): super().__init__(*args, **kwargs) # Limit the enrolments to those connected with the invoice self.fields['enrolment'].queryset = (Enrolment.objects.filter( ledger__invoice=invoice).distinct().select_related( 'module').order_by('-id')) # Dropdown options display the module title self.fields[ 'enrolment'].label_from_instance = lambda obj: f'{obj.module.code} - {obj.module} (£{obj.get_balance():.2f})'
class GuestSpeakerForm(ContractForm): topic = forms.CharField(label='Lecture topic(s)') dates_and_times = forms.CharField( help_text=mark_safe('When the lectures will take place. E.g. <i>12 Jan 2020 at 9:45, 11:15, and 13:30</i>'), ) lecture_no = forms.IntegerField(label='Number of lectures', initial=1, min_value=1, max_value=100) fee_per_lecture = forms.DecimalField( max_digits=8, decimal_places=2, max_value=10000, min_value=0, widget=PoundInput() ) field_order = [ 'phone', 'email', 'topic', 'venue', 'dates_and_times', 'lecture_no', 'fee_per_lecture', 'approver', 'return_to', 'return_address', 'email_notification', ]
class InvoiceSearchFilter(django_filters.FilterSet): invoiced_to = django_filters.CharFilter(field_name='invoiced_to', label='Invoiced to', lookup_expr='icontains') minimum = django_filters.NumberFilter( field_name='amount', label='Minimum amount', lookup_expr='gte', widget=PoundInput() ) maximum = django_filters.NumberFilter( field_name='amount', label='Maximum amount', lookup_expr='lte', widget=PoundInput() ) created_by = django_filters.CharFilter(field_name='created_by', label='Created by (username)', lookup_expr='exact') created_after = django_filters.DateFilter( field_name='created_on', label='Created on or after', lookup_expr='gte', widget=DatePickerInput() ) def filter_address(self, queryset, field_name, value): """Filters on any invoice address field""" if value: return queryset.filter( Q(line1__icontains=value) | Q(line2__icontains=value) | Q(line3__icontains=value) | Q(town__icontains=value) | Q(countystate__icontains=value) | Q(country__icontains=value) | Q(postcode__icontains=value) ) return queryset address = django_filters.CharFilter( label='Address', method='filter_address', help_text="E.g. 'OX1 2JA', 'Sacramento', or 'Mexico'", ) def overdue_only(self, queryset, field_name, value): if value: return queryset.overdue() return queryset overdue = django_filters.BooleanFilter( label='Overdue only?', method='overdue_only', widget=forms.CheckboxInput, ) def outstanding_only(self, queryset, field_name, value): if value: return queryset.outstanding() return queryset outstanding = django_filters.BooleanFilter( label='Outstanding only?', method='outstanding_only', widget=forms.CheckboxInput, ) class Meta: model = Invoice fields = [ 'invoiced_to', 'address', 'minimum', 'maximum', 'created_by', 'created_after', 'overdue', 'outstanding', ]
class Meta: model = models.Ledger fields = ('amount', 'narrative', 'type') widgets = {'amount': PoundInput()}
class Meta: model = models.Ledger fields = ('amount', 'narrative', 'account', 'type') widgets = {'amount': PoundInput()} help_texts = {'amount': 'Positive values increase debt, negative decrease debt'}