在调用save()之前,根据max_digits和decimal_places属性自动舍入Django的DecimalField

时间:2016-06-22 03:38:55

标签: python django

我想在ModelForm中调用save()函数之前根据max_digits和decimal_places属性自动舍入Django的DecimalField。

目前正在使用以下内容:

  • django 1.8
  • python 2.7

到目前为止我的尝试。

https://djangosnippets.org/snippets/10554/

models.py

amount = models.DecimalField(max_digits = 19, decimal_places = 2)

views.py

P.S。将它应用于不同的领域和不同的模型

data = {"amount" : 100.1234,"name":"John Doe",...}
form = My_form(data)
if form.is_valid(): //the error throws from here.
    form.save()
else:
    raise ValueError(form.errors)

forms.py

我计划清理clean()函数中的字段并对所有十进制字段进行舍入,但是当我尝试打印raw_data时,没有' amount字段'。

class My_form(forms.ModelForm):
    Class Meta:
        model = My_model
        fields = ('amount','name')
    def clean(self):
        raw_data = self.cleaned_data
        print(raw_data) //only prints {'name' : 'John Doe'}

3 个答案:

答案 0 :(得分:4)

您主要收到错误,因为forms.DecimalFieldmodels.DecimalField的单独验证者:

data = {'amount': 1.12345 }

class NormalForm(forms.Form):
    amount = forms.DecimalField(max_digits = 19, decimal_places = 2)

normal_form = NormalForm(data)
normal_form.is_valid()  # returns False
normal_form.cleaned_data  # returns {}
对于包含类forms.DecimalField字段的模型,默认情况下使用

models.DecimalField。你可以这样做:

from django import forms
from django.db import models
from decimal import Decimal

def round_decimal(value, places):
    if value is not None:
        # see https://docs.python.org/2/library/decimal.html#decimal.Decimal.quantize for options
        return value.quantize(Decimal(10) ** -places)
    return value

class RoundingDecimalFormField(forms.DecimalField):
    def to_python(self, value):
        value = super(RoundingDecimalFormField, self).to_python(value)
        return round_decimal(value, self.decimal_places)

class RoundingDecimalModelField(models.DecimalField):
    def to_python(self, value):
        # you could actually skip implementing this
        value = super(RoundingDecimalModelField, self).to_python(value)
        return round_decimal(value, self.decimal_places)

    def formfield(self, **kwargs):
        defaults = { 'form_class': RoundingDecimalFormField }
        defaults.update(kwargs)
        return super(RoundingDecimalModelField, self).formfield(**kwargs)

现在,只要您使用models.DecimalField,请改用RoundingDecimalModelField。您与这些模型一起使用的任何表单现在也将使用自定义表单字段。

class RoundingForm(forms.Form):
    amount = RoundingDecimalFormField(max_digits = 19, decimal_places = 2)

data = {'amount': 1.12345 }

rounding_form = RoundingForm(data)
rounding_form.is_valid()  # returns True
rounding_form.cleaned_data  # returns {'amount': Decimal('1.12')}

答案 1 :(得分:2)

如果要直接分配给模型实例,则无需担心。字段对象将量化值(将其舍入)到您在模型定义中设置的小数点级别。

如果您正在处理ModelForm,则默认DecimalField将要求任何输入与模型字段的小数点匹配。一般来说,处理这个问题的最简单方法可能是继承模型DecimalField,删除特定于小数的验证器,并依靠底层转换来量化数据,如下所示:

from django.db.models.fields import DecimalField

class RoundingDecimalField(DecimalField):

    @cached_property
    def validators(self):
        return super(DecimalField, self).validators

    def formfield(self, **kwargs):
        defaults = {
            'max_digits': self.max_digits,
            'decimal_places': 4, # or whatever number of decimal places you want your form to accept, make it a param if you like
            'form_class': forms.DecimalField,
        }
        defaults.update(kwargs)
        return super(RoundingDecimalField, self).formfield(**defaults)

然后在你的模特中:

amount = RoundingDecimalField(max_digits = 19, decimal_places = 2)

(实际上不要将字段类放在与模型相同的字段中,仅作为示例。)

在绝对意义上,这可能不如定义自定义字段表单那么正确,这是我的第一个建议,但使用的工作量较少。

答案 2 :(得分:0)

如果要设置窗口小部件以限制仅使用此代码的十进制数输入表单呈现

from django import forms

class DecimalNumberInput(forms.NumberInput):
    def get_context(self, name, value, attrs):

        context = super().get_context(name, value, attrs)
        try:
            if self.attrs['decimal_places'] and isinstance(self.attrs['decimal_places'], int) :
                context['widget']['value'] = str(round(float(context['widget']['value']),self.attrs['decimal_places']))
        except Exception as e:
            pass
        return context
class NormalForm(forms.Form):
    amount = forms.DecimalField(max_digits = 19, decimal_places = 2 , widget=DecimalNumberInput(attrs={'decimal_places':2}))