Dear all,
I created the following to have a PercentageField in my Form, with the proper validations, while saving it in the database as a fraction. After several tries I have the following solution.
class PercentageField(forms.DecimalField):
"""Form field that converts between percent (UI) and fraction (model)."""
def __init__(self, *args, **kwargs):
kwargs.setdefault('max_digits', 4)
kwargs.setdefault('decimal_places', 1)
kwargs.setdefault('widget', forms.NumberInput(attrs={'class': 'form-control'}))
kwargs.setdefault('validators', [MinValueValidator(0), MaxValueValidator(100)])
super().__init__(*args, **kwargs)
def prepare_value(self, value):
# Converts an internal fraction Decimal (e.g., Decimal('0.215'))
# into a percentage Decimal for display by the widget.
# This is called for initial form data.
if value is None:
return None
print("Preparing value for percentage field")
return (value * Decimal('100')).quantize(Decimal('0.1'))
def clean(self, value):
# This method is called when the form is validated.
value = super().clean(value)
if value is None:
return None
return Decimal(value) / Decimal('100') # Convert percentage to fraction
def bound_data(self, data, initial):
# This method is called when the form is bound (e.g., on POST).
# It provides the value that will be passed to the widget for redisplay if there are errors.
# We return `data` (the raw user input string) to ensure the widget displays
# exactly what the user typed, preventing `prepare_value` from re-multiplying it.
return self.to_python(data) / Decimal('100.0')
Is this the way to do it? Or can I catch somewhere if the form is_bound in the prepare value?
Copilot suggested just to change the clean + prepare value functions for example, but this would result in multiplications on form errors. So a percentage entered as 10 became 1000 after the form was reprocessed with the error.