Atma
Atma

Reputation: 29805

how to have an IntegerField in a form for a DecimalField in the model in django?

I am trying to do the following:

class AmountInfoForm(forms.ModelForm):

    amount = forms.IntegerField()

    class Meta:
        model = Customer
        fields = ('amount',)

For my model field:

class Customer(models.Model):
    amount = models.DecimalField(max_digits=6, decimal_places=2, null=True)

This is so that values that show in my text input are round as opposed to showing decimal places.

The problem is that decimal places still show.

It only accepts the number 60 for example as input value. However when it shows the form instance of the field, it shows 60.00 What am I doing wrong?

Upvotes: 2

Views: 6382

Answers (2)

Jarad
Jarad

Reputation: 18983

Alternative 2022 answer

Usage: if you enter 60 in the creation form, form redirects you to update form and displays 60 in the field (not 60.00). In the database, the amount column shows 0.6. The database field is decimal type.

urls.py

from django.urls import path
from .views import AmountCreateView, AmountUpdateView

app_name = 'customers'
urlpatterns = [
    path('', AmountCreateView.as_view(), name='create'),
    path('<int:pk>/', AmountUpdateView.as_view(), name='update'),
]

models.py

from django.db import models
from django.shortcuts import reverse


class Customer(models.Model):
    amount = models.DecimalField(max_digits=6, decimal_places=2, null=True)

    def get_absolute_url(self):
        return reverse("customers:update", kwargs={'pk': self.pk})

widgets.py

from django.forms.widgets import NumberInput
import decimal


class CustomWidget(NumberInput):

    def format_value(self, value):
        if value is None:
            return value
        if isinstance(value, decimal.Decimal):
            # it should return this if everything's OK
            return str(int(decimal.Decimal(value) * 100))
        return super().format_value(value)

forms.py

from django import forms
from .models import Customer
from .widgets import CustomWidget
import decimal
from django.utils import formats
from django.core.exceptions import ValidationError

class CustomIntegerField(forms.IntegerField):
    widget = CustomWidget

    def to_python(self, value):
        if value in self.empty_values:
            return None
        if self.localize:
            value = formats.sanitize_separators(value)
        value = str(value).strip()
        try:
            value = decimal.Decimal(value) / 100
        except decimal.DecimalException:
            raise ValidationError(self.error_messages['invalid'], code='invalid')
        return value

class AmountInfoForm(forms.ModelForm):
    amount = CustomIntegerField()

    class Meta:
        model = Customer
        fields = ('amount',)

views.py

from django.views.generic import FormView, UpdateView
from .forms import AmountInfoForm
from .models import Customer

class AmountCreateView(FormView):
    template_name = 'home.html'
    form_class = AmountInfoForm

    def form_valid(self, form):
        self.object = form.save()
        return super().form_valid(form)

    def get_success_url(self):
        return self.object.get_absolute_url()


class AmountUpdateView(UpdateView):
    template_name = 'home.html'
    form_class = AmountInfoForm
    model = Customer

home.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Home</title>
</head>
<body>
    <form method="POST">
        {% csrf_token %}
        {{ form.as_p }}
        <input type="submit" value="Submit">
    </form>

</body>
</html>

Upvotes: 0

madzohan
madzohan

Reputation: 11808

1) Django field is not Django widget, default widget for DecimalField and for IntegerField is TextInput.

And from this note your declaration overrides default behaviour of amount field. But data stored in Decimal type not int so if you want represent it in int you should do smth like this:

int(Decimal())

Or read Decimal docs and set custom rounding.

2) At this step you need smth to specify your custom rendering, so there are number of variation how you can do it (at this moment I remember two of them):

2.1) Simple one - override initial data in ModelForm init:

class AmountInfoForm(forms.ModelForm):
    class Meta:
        model = Customer
        fields = ('amount',)

    def __init__(self, *args, **kwargs):
        super(AmountInfoForm, self).__init__(*args, **kwargs)

        amount_initial = self.initial.get('amount')
        if amount_initial:
            self.initial['amount'] = int(amount_initial)

2.2) If you need custom representation in fancy widget you can override TextInput (look at the source) and specify it in your class Meta docs :

class YourCustomWidget(TextInput):
    def render(self, name, value, attrs=None):
        if value is None:
            value = ''
        final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
        if value != '':
            # Only add the 'value' attribute if a value is non-empty.
            custom_value = int(value)
            final_attrs['value'] = force_text(self._format_value(custom_value))

        # here you can customise output html
        return format_html('<input{0} />', flatatt(final_attrs))

class AmountInfoForm(forms.ModelForm):
    class Meta:
        model = Customer
        fields = ('amount',)
        widgets = {
            'amount': YourCustomWidget(),
        }

Upvotes: 3

Related Questions