Changeset View
Changeset View
Standalone View
Standalone View
looper/form_fields.py
| import typing | from typing import Any, Mapping, Optional, Union | ||||
| from django import forms | from django import forms | ||||
| from django.db.models import Model | |||||
| from django.forms import NumberInput | from django.forms import NumberInput | ||||
| from .money import Money | from looper.money import Money | ||||
| class MoneyInput(NumberInput): | class MoneyInput(NumberInput): | ||||
| def __init__(self, attrs: typing.Optional[dict] = None) -> None: | def __init__(self, attrs: Optional[Mapping[str, str]] = None) -> None: | ||||
| super().__init__({ | super().__init__({'placeholder': '123.45', **(attrs or {})}) | ||||
| 'placeholder': '123.45', | |||||
| **(attrs or {}), | |||||
| }) | |||||
| class MoneyFormField(forms.FloatField): | class MoneyFormField(forms.FloatField): | ||||
| """Juggle int (cents), float (units of currency), and Money.""" | """Juggle int (cents), float (units of currency), and Money.""" | ||||
| def clean(self, value: str) -> typing.Optional[int]: | def clean(self, value: str) -> Optional[int]: | ||||
| """Convert from float-as-string to integer number of cents.""" | """Convert from float-as-string to integer number of cents.""" | ||||
| as_float = super().clean(value) | as_float = super().clean(value) | ||||
| if as_float is None: | if as_float is None: | ||||
| return None | return None | ||||
| return int(round(as_float * 100)) | return int(round(as_float * 100)) | ||||
| def prepare_value(self, value: typing.Union[None, int, str, Money]) -> typing.Optional[str]: | def prepare_value(self, value: Union[None, int, str, Money]) -> Optional[str]: | ||||
| """Turn a possible input value into a string for display in the widget. | """Turn a possible input value into a string for display in the widget. | ||||
| String input is interpreted as floats (e.g. units of currency). | String input is interpreted as floats (e.g. units of currency). | ||||
| Int input is interpreted as cents. | Int input is interpreted as cents. | ||||
| Money is money. | Money is money. | ||||
| """ | """ | ||||
| if value is None: | if value is None: | ||||
| return None | return None | ||||
| if isinstance(value, str): | if isinstance(value, str): | ||||
| if value == '': | if value == '': | ||||
| return None | return None | ||||
| value = int(round(float(value) * 100)) | value = int(round(float(value) * 100)) | ||||
| if isinstance(value, int): | if isinstance(value, int): | ||||
| value = Money('', value) | value = Money('', value) | ||||
| assert isinstance(value, Money) | assert isinstance(value, Money) | ||||
| return value.decimals_string | return value.decimals_string | ||||
| def has_changed(self, | def has_changed( | ||||
| initial: typing.Union[None, int, str, Money], | self, initial: Union[None, int, str, Money], data: Union[None, int, str, Money], | ||||
| data: typing.Union[None, int, str, Money]) -> bool: | ) -> bool: | ||||
| return self.prepare_value(initial) != self.prepare_value(data) | return self.prepare_value(initial) != self.prepare_value(data) | ||||
| class GatewayRadioSelect(forms.RadioSelect): | class GatewayRadioSelect(forms.RadioSelect): | ||||
| option_template_name = 'looper/forms/widgets/gateway_radio_option.html' | option_template_name = 'looper/forms/widgets/gateway_radio_option.html' | ||||
| def create_option(self, name, value, label, selected, index, subindex=None, attrs=None) -> dict: | def create_option(self, name, value, label, selected, index, subindex=None, attrs=None) -> dict: | ||||
| gateway = self.choices.queryset.get(name=value) | gateway = self.choices.queryset.get(name=value) | ||||
| return { | return { | ||||
| **super().create_option(name, value, label, selected, index, subindex=subindex, | **super().create_option( | ||||
| attrs=attrs), | name, value, label, selected, index, subindex=subindex, attrs=attrs | ||||
| ), | |||||
| 'form_description': gateway.form_description, | 'form_description': gateway.form_description, | ||||
| } | } | ||||
| class GatewayChoiceField(forms.ModelChoiceField): | class GatewayChoiceField(forms.ModelChoiceField): | ||||
| """ModelChoiceField for choosing a payment gateway. | """ModelChoiceField for choosing a payment gateway. | ||||
| Stores the gateway name in the field value. This allows JavaScript to know | Stores the gateway name in the field value. This allows JavaScript to know | ||||
| which gateway is selected without having to resort to hard-coding primary | which gateway is selected without having to resort to hard-coding primary | ||||
| keys. | keys. | ||||
| """ | """ | ||||
| def __init__(self, **kwargs) -> None: | def __init__(self, **kwargs: Any) -> None: | ||||
| from . import models | from looper import models | ||||
| kwargs.setdefault('empty_label', None) | kwargs.setdefault('empty_label', None) | ||||
| kwargs.setdefault('label', 'Payment method') | kwargs.setdefault('label', 'Payment method') | ||||
| kwargs.setdefault('queryset', models.Gateway.objects.all()) | kwargs.setdefault('queryset', models.Gateway.objects.all()) | ||||
| kwargs.setdefault('to_field_name', 'name') | kwargs.setdefault('to_field_name', 'name') | ||||
| kwargs.setdefault('widget', GatewayRadioSelect()) | kwargs.setdefault('widget', GatewayRadioSelect()) | ||||
| super().__init__(**kwargs) | super().__init__(**kwargs) | ||||
| def label_from_instance(self, obj) -> str: | def label_from_instance(self, obj: 'Model') -> str: | ||||
| from looper import models | |||||
| assert isinstance(obj, models.Gateway) | |||||
| return obj.frontend_name | return obj.frontend_name | ||||