#Decimal() in a choice field

6 messages · Page 1 of 1 (latest)

spiral hollow
#

Hello, I have a Decimal field that can contain a number of Decimal() values (it's V.A.T. options):

CHOIX_TVA = [
        (Decimal(0), "0"),
        (Decimal(5.5), "5.5 %"),
        (Decimal(10), "10 %"),
        (Decimal(20), "20 %"),
    ]

tva = models.DecimalField(max_digits=20, decimal_places=2, validators=[MinValueValidator(0), MaxValueValidator(100)])

When I load this field in a form, say :

class OpportuniteTvaForm(forms.ModelForm):
    class Meta:
        model = Opportunite
        fields = ['tva', ]

    tva = forms.ChoiceField(
        required=False,
        widget=forms.widgets.Select(attrs={"class": "form-select"}),
        choices=Opportunite.CHOIX_TVA,
        label="tva"
    )

The rendered form will not have Decimal options in the select field :

<select name="tva" class="form-select" id="id_tva">
  <option value="0">0</option>
  <option value="5.5">5.5 %</option>
  <option value="10">10 %</option>
  <option value="20">20 %</option>
</select>

Saving the data seems to work, but if I load the form bound to an existing instance, then the initial data Decimal(5.5) will not match the option value="5.5", and the field is not preselected to the right option.

Here is an example of a log entry with the form's initial data:

web    | 2026-02-14 14:02:05 INFO    views 1288 | {'tva': Decimal('5.50')}

is it expected that forms.ChoiceField() manages the conversion from Decimal() to the HTML equivalent option, or should we handle this manually ?

Thanks for your help in advance, I hope the question is clear enough !

cinder sluice
#

The Select widget is checking the stringified version of the keys. The database is returning the tva as a Decimal(5.50) and str(Decimal(5.50)) == '5.50' whereas the choice string value is '5.5'. This means it doesn't see it as selected, even though the actual Decimal() instances are equivalent in python.

Try adding the trailing decimal places to CHOIX_TVA.

spiral hollow
#

Thanks a lot for the pointers - I made some tests but could never get to have a proper result, I tried with basic int/floats, strings and Decimal().
When I test in shell mode str(Decimal(5.5)) = str(Decimal(5.50)) = '5.5' quite consistently. So it seems it should just work. It's as if the str() part is not executed, and the actual Decimal() is passed.

I ended up making a kind of translation table in get_form() which I dont like but does work :


class OpportuniteDateTvaUpdateHxView(MyUpdateView):
    model = Opportunite
    form_class = OpportuniteDateTvaForm
    template_name = 'opportunites/partials/opportunite_date_tva_edit_modal.html'
    context_object_name = 'opportunite'
    success_url = reverse_lazy('home')  # dummy success_url, unused (bypassed a form_valid)

    def get_form(self) -> OpportuniteDateTvaForm:
        self.form = super().get_form(self.form_class)
        tva_map = {
            Decimal('0.00'): '0',
            Decimal('5.50'): '5.5',
            Decimal('10.00'): '10',
            Decimal('20.00'): '20'
        }
        i = self.form.initial['tva']
        if i in tva_map:
            self.form.initial['tva'] = tva_map[i]
        logger.info(f"{self.form.initial}")
        return self.form

    def form_valid(self, form: OpportuniteDateTvaForm) -> HttpResponse:
        logger.info(f"cleaned form data: {form.cleaned_data}")
        super().form_valid(form)
        partial_name = 'opportunites/partials/opportunite_generic_attributes.html'
        opportunite_pk = self.kwargs.get('pk')
        opportunite = Opportunite.objects.get(pk=opportunite_pk)
        context = {
            'opportunite': opportunite,
        }
        return render(self.request, partial_name, context)
cinder sluice
#

Did you try changing your choices constant like I suggested

#

It's curiousity on my end that's all

spiral hollow
#

Yes I did, I think I tested all possible choices configurations, removed some decimals after the comma, and switched to just integers and floats but it seems I’m missing something