diff options
Diffstat (limited to 'talerbank/app/models.py')
-rw-r--r-- | talerbank/app/models.py | 242 |
1 files changed, 124 insertions, 118 deletions
diff --git a/talerbank/app/models.py b/talerbank/app/models.py index f869ea3..08da111 100644 --- a/talerbank/app/models.py +++ b/talerbank/app/models.py @@ -23,179 +23,185 @@ from typing import Any, Tuple from django.contrib.auth.models import User from django.db import models from django.conf import settings -from django.core.exceptions import \ - ValidationError, \ - ObjectDoesNotExist -from taler.util.amount import Amount, BadFormatAmount, NumberTooBig, CurrencyMismatch +from django.core.exceptions import ValidationError, ObjectDoesNotExist +from taler.util.amount import Amount, SignedAmount, CurrencyMismatchError -class InvalidAmount(Amount): - def __init__(self, currency): - super(InvalidAmount, self - ).__init__(currency, value=float('nan'), fraction=float('nan')) - def stringify(self, ndigits, pretty): - return "Invalid Amount, please report" +def get_zero_amount() -> Amount: + """ + Helper function that instantiates a zero-valued Amount + object in the currency that the bank runs on. + """ + return Amount(settings.TALER_CURRENCY, 0, 0) - def dump(self): - return "Invalid Amount, please report" +def get_zero_signed_amount() -> SignedAmount: + """ + Helper function that instantiates a zero-valued SignedAmount + object in the currency that the bank runs on. + """ + return SignedAmount(True, get_zero_amount()) -## -# Helper function that instantiates a zero-valued @a Amount -# object. -def get_zero_amount() -> Amount: - return Amount(settings.TALER_CURRENCY) + +class SignedAmountField(models.Field): + """Custom implementation of the SignedAmount class as a database type.""" + + description = "Signed amount object in Taler style" + + def db_type(self, connection: Any) -> str: + """ + Return the database type of the serialized amount. + """ + return "varchar" + + def get_prep_value(self, value: SignedAmount) -> str: + """ + Stringifies the Amount object to feed the DB connector. + """ + c = value.amount.currency + if settings.TALER_CURRENCY != c: + raise CurrencyMismatchError(settings.TALER_CURRENCY, c) + return value.stringify() + + @staticmethod + def from_db_value(value: str, *args) -> Amount: + """ + Parse the stringified Amount back to Python. + + Parameters + ---------- + value : str + Serialized amount coming from the database. + (String in the usual CURRENCY:X.Y format) + args : any + Unused + """ + del args # pacify PEP checkers + return SignedAmount.parse(value) + + def to_python(self, value: Any) -> Amount: + """ + Parse the stringified Amount back to Python. FIXME: + why this serializer consider _more_ cases respect to the + one above ('from_db_value')? + + Parameters + ---------- + value: serialized amount coming from the database + + """ + + if isinstance(value, SignedAmount): + return value + try: + return SignedAmount.parse(value) + except BadFormatAmount: + raise ValidationError( + "Invalid input for a signed amount string: %s" % value + ) -## -# Custom implementation of the @a Amount class as a database type. class AmountField(models.Field): - description = 'Amount object in Taler style' - - ## - # Return the database type of the serialized amount. - # - # @param self the object itself. - # @param connection the database connection. - # @return type of the serialized amount: varchar. + """Custom implementation of the Amount class as a database type.""" + + description = "Amount object in Taler style" + def db_type(self, connection: Any) -> str: + """ + Return the database type of the serialized amount. + """ return "varchar" - ## - # Stringifies the Amount object to feed the DB connector. - # - # @param self the object itself. - # @para value the @a Amount object to be serialized. def get_prep_value(self, value: Amount) -> str: - if not value: - return "%s:0.0" % settings.TALER_CURRENCY + """ + Stringifies the Amount object to feed the DB connector. + """ if settings.TALER_CURRENCY != value.currency: - raise CurrencyMismatch(settings.TALER_CURRENCY, value.currency) - return value.stringify(settings.TALER_DIGITS) - - ## - # Parse the stringified Amount back to Python. - # - # @param value serialized amount coming from the database. - # (It is just a string in the usual CURRENCY:X.Y form) - # @param args currently unused. - # @return the @a Amount object. + raise CurrencyMismatchError(settings.TALER_CURRENCY, value.currency) + return value.stringify() + @staticmethod def from_db_value(value: str, *args) -> Amount: + """ + Parse the stringified Amount back to Python. + + Parameters + ---------- + value : str + Serialized amount coming from the database. + (String in the usual CURRENCY:X.Y format) + args : any + Unused + """ del args # pacify PEP checkers - if value is None: - return Amount.parse(settings.TALER_CURRENCY) - try: - return Amount.parse(value) - except NumberTooBig: - # Keep the currency right to avoid causing - # exceptions if some operation is attempted - # against this invalid amount. NOTE that the - # value is defined as NaN, so no actual/useful - # amount will ever be generated using this one. - # And more, the NaN value will make it easier - # to scan the database to find these faulty - # amounts. - # We also decide to not raise exception here - # because they would propagate in too many places - # in the code, and it would be too verbose to - # just try-cactch any possible exception situation. - return InvalidAmount(settings.TALER_CURRENCY) - - ## - # Parse the stringified Amount back to Python. FIXME: - # why this serializer consider _more_ cases respect to the - # one above ('from_db_value')? - # - # @param value serialized amount coming from the database. - # (It is just a string in the usual CURRENCY:X.Y form) - # @param args currently unused. - # @return the @a Amount object. + return Amount.parse(value) + def to_python(self, value: Any) -> Amount: + """ + Parse the stringified Amount back to Python. FIXME: + why this serializer consider _more_ cases respect to the + one above ('from_db_value')? + + Parameters + ---------- + value: serialized amount coming from the database + + """ + if isinstance(value, Amount): return value try: - if value is None: - return Amount.parse(settings.TALER_CURRENCY) return Amount.parse(value) except BadFormatAmount: raise ValidationError("Invalid input for an amount string: %s" % value) -class BankAccountDoesNotExist(Exception): - def __init__(self, msg): - super(BankAccountDoesNotExist, self).__init__(msg) - self.hint = msg - self.http_status_code = 404 - self.taler_error_code = 5110 - -class BankTransactionDoesNotExist(Exception): - def __init__(self, msg): - super(BankTransactionDoesNotExist, self).__init__(msg) - self.hint = msg - self.http_status_code = 404 - self.taler_error_code = 5111 def join_dict(**inputDict): - return ", ".join(['%s==%s' % (key, value) for (key, value) in inputDict.items()]) + return ", ".join(["%s==%s" % (key, value) for (key, value) in inputDict.items()]) -class CustomManager(models.Manager): - def __init__(self): - super(CustomManager, self).__init__() - - def get_queryset(self): - return models.QuerySet(self.model, using=self._db) - - def get(self, *args, **kwargs): - try: - return super(CustomManager, self).get(*args, **kwargs) - except BankAccount.DoesNotExist: - raise BankAccountDoesNotExist(f"Bank account not found for {join_dict(**kwargs)}") - except BankTransaction.DoesNotExist: - raise BankTransactionDoesNotExist(f"Bank transaction not found for {join_dict(**kwargs)}") - -## -# The class representing a bank account. class BankAccount(models.Model): + """ + The class representing a bank account. + """ + is_public = models.BooleanField(default=False) - debit = models.BooleanField(default=False) account_no = models.AutoField(primary_key=True) user = models.OneToOneField(User, on_delete=models.CASCADE) - amount = AmountField(default=get_zero_amount) - objects = CustomManager() + balance = SignedAmountField(default=get_zero_signed_amount) + -## -# The class representing a bank transaction. class BankTransaction(models.Model): + """ + The class representing a bank transaction. + """ + amount = AmountField(default=False) debit_account = models.ForeignKey( BankAccount, on_delete=models.CASCADE, db_index=True, - related_name="debit_account" + related_name="debit_account", ) credit_account = models.ForeignKey( BankAccount, on_delete=models.CASCADE, db_index=True, - related_name="credit_account" + related_name="credit_account", ) subject = models.CharField(default="(no subject given)", max_length=200) date = models.DateTimeField(auto_now=True, db_index=True) cancelled = models.BooleanField(default=False) - objects = CustomManager() class TalerWithdrawOperation(models.Model): - withdraw_id = models.UUIDField( - primary_key=True, default=uuid.uuid4, editable=False - ) + withdraw_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) amount = AmountField(default=False) withdraw_account = models.ForeignKey( BankAccount, on_delete=models.CASCADE, db_index=True, - related_name="withdraw_account" + related_name="withdraw_account", ) selection_done = models.BooleanField(default=False) withdraw_done = models.BooleanField(default=False) @@ -203,6 +209,6 @@ class TalerWithdrawOperation(models.Model): BankAccount, null=True, on_delete=models.CASCADE, - related_name="selected_exchange_account" + related_name="selected_exchange_account", ) selected_reserve_pub = models.TextField(null=True) |