enforce max amount on sith account

This commit is contained in:
imperosol
2026-06-07 14:16:12 +02:00
parent 1957aa1fdb
commit f6f31af975
4 changed files with 92 additions and 11 deletions
+49 -4
View File
@@ -1,22 +1,67 @@
from decimal import Decimal from decimal import Decimal
from django.conf import settings from django.conf import settings
from django.core import checks
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models from django.db import models
from django.utils.functional import cached_property
class CurrencyField(models.DecimalField): class CurrencyField(models.DecimalField):
"""Custom database field used for currency.""" """Custom database field used for currency."""
def __init__(self, *args, **kwargs): def __init__(
kwargs["max_digits"] = 12 self, verbose_name=None, name=None, min_value=None, max_value=None, **kwargs
kwargs["decimal_places"] = 2 ):
super().__init__(*args, **kwargs) kwargs.update({"max_digits": 12, "decimal_places": 2})
self.min_value = min_value
self.max_value = max_value
super().__init__(verbose_name, name, **kwargs)
def to_python(self, value): def to_python(self, value):
if value is None: if value is None:
return None return None
return super().to_python(value).quantize(Decimal("0.01")) return super().to_python(value).quantize(Decimal("0.01"))
@cached_property
def validators(self):
res = []
if self.max_value:
res.append(MaxValueValidator(self.max_value))
if self.min_value:
res.append(MinValueValidator(self.min_value))
return [*super().validators, *res]
def check(self, **kwargs):
errors = super().check(**kwargs)
for name, val in ("min_value", self.min_value), ("max_value", self.max_value):
if not val:
continue
try:
float(val)
except ValueError:
errors.append(
checks.Error(
f"CurrencyField.{name} must be a valid float",
obj=self,
id="sith.E001",
)
)
return errors
def formfield(self, **kwargs):
return super().formfield(
**{"min_value": self.min_value, "max_value": self.max_value, **kwargs}
)
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
if self.min_value is not None:
kwargs["min_value"] = self.min_value
if self.max_value is not None:
kwargs["max_value"] = self.max_value
return name, path, args, kwargs
if settings.TESTING: if settings.TESTING:
from model_bakery import baker from model_bakery import baker
@@ -0,0 +1,30 @@
# Generated by Django 5.2.15 on 2026-06-07 12:08
from django.db import migrations
import counter.fields
class Migration(migrations.Migration):
dependencies = [("counter", "0041_alter_billinginfo_country_and_more")]
operations = [
migrations.AlterField(
model_name="customer",
name="amount",
field=counter.fields.CurrencyField(
decimal_places=2,
default=0,
max_digits=12,
max_value=250,
verbose_name="amount",
),
),
migrations.AlterField(
model_name="refilling",
name="amount",
field=counter.fields.CurrencyField(
decimal_places=2, max_digits=12, min_value=0.01, verbose_name="amount"
),
),
]
+11 -7
View File
@@ -28,7 +28,7 @@ from dict2xml import dict2xml
from django.conf import settings from django.conf import settings
from django.core.validators import MinLengthValidator from django.core.validators import MinLengthValidator
from django.db import models from django.db import models
from django.db.models import Exists, F, OuterRef, Q, QuerySet, Subquery, Sum, Value from django.db.models import Exists, F, Max, OuterRef, Q, QuerySet, Subquery, Sum, Value
from django.db.models.functions import Coalesce, Concat, Length from django.db.models.functions import Coalesce, Concat, Length
from django.forms import ValidationError from django.forms import ValidationError
from django.urls import reverse from django.urls import reverse
@@ -99,7 +99,9 @@ class Customer(models.Model):
user = models.OneToOneField(User, primary_key=True, on_delete=models.CASCADE) user = models.OneToOneField(User, primary_key=True, on_delete=models.CASCADE)
account_id = models.CharField(_("account id"), max_length=10, unique=True) account_id = models.CharField(_("account id"), max_length=10, unique=True)
amount = CurrencyField(_("amount"), default=0) amount = CurrencyField(
_("amount"), max_value=settings.SITH_ACCOUNT_MAX_MONEY, default=0
)
objects = CustomerQuerySet.as_manager() objects = CustomerQuerySet.as_manager()
@@ -156,13 +158,15 @@ class Customer(models.Model):
unique_fields=["customer", "returnable"], unique_fields=["customer", "returnable"],
) )
@property @cached_property
def can_buy(self) -> bool: def can_buy(self) -> bool:
"""Check if whether this customer has the right to purchase any item.""" """Check if whether this customer has the right to purchase any item."""
subscription = self.user.subscriptions.order_by("subscription_end").last() subscription_end = self.user.subscriptions.aggregate(
if subscription is None: res=Max("subscription_end")
).get("res")
if subscription_end is None:
return False return False
return (date.today() - subscription.subscription_end) < timedelta(days=90) return (date.today() - subscription_end) < timedelta(days=90)
@classmethod @classmethod
def get_or_create(cls, user: User) -> tuple[Customer, bool]: def get_or_create(cls, user: User) -> tuple[Customer, bool]:
@@ -823,7 +827,7 @@ class Refilling(models.Model):
counter = models.ForeignKey( counter = models.ForeignKey(
Counter, related_name="refillings", blank=False, on_delete=models.CASCADE Counter, related_name="refillings", blank=False, on_delete=models.CASCADE
) )
amount = CurrencyField(_("amount")) amount = CurrencyField(_("amount"), min_value=0.01)
operator = models.ForeignKey( operator = models.ForeignKey(
User, User,
related_name="refillings_as_operator", related_name="refillings_as_operator",
+2
View File
@@ -503,6 +503,8 @@ SITH_ACCOUNT_INACTIVITY_DELTA = relativedelta(years=2)
SITH_ACCOUNT_DUMP_DELTA = timedelta(days=30) SITH_ACCOUNT_DUMP_DELTA = timedelta(days=30)
"""timedelta between the warning mail and the actual account dump""" """timedelta between the warning mail and the actual account dump"""
SITH_ACCOUNT_MAX_MONEY = 250 # €
# Defines which product type is the refilling type, # Defines which product type is the refilling type,
# and thus increases the account amount # and thus increases the account amount
SITH_COUNTER_PRODUCTTYPE_REFILLING = env.int( SITH_COUNTER_PRODUCTTYPE_REFILLING = env.int(