【问题标题】:Automatically round Django's DecimalField according to the max_digits and decimal_places attributes before calling save()在调用 save() 之前根据 max_digits 和 decimal_places 属性自动舍入 Django 的 DecimalField
【发布时间】:2016-10-23 19:27:51
【问题描述】:

我想在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

附:将它应用到不同的领域和不同的模型中

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 时,没有“金额字段”。

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'}

【问题讨论】:

    标签: python django


    【解决方案1】:

    您收到错误主要是因为forms.DecimalField 与models.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')}
    

    【讨论】:

    • 现在可以使用了。我只是在我的表单字段中添加了“RoundingDecimalFormField”,而没有更改“models.DecimalField”。不过,我需要在所有表单中都这样做真的很痛苦。
    • 感谢@Vin-G,你真的拯救了我的一天。
    • 对,如果您的表单字段为您进行量化,您实际上不需要更新模型字段的to_python,如下所示。在自定义模型字段上使用 formfield 方法比我第一次使用它是一个很好的改进。而且因为验证器是在表单字段的to_python 之后运行的,所以您不需要更改它们。如果您使用此处显示的带有formfield 的更新模型字段,则不需要更新表单,除非您已经覆盖了字段设置。
    【解决方案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)
    

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

    这在绝对意义上可能不如定义自定义字段表单正确,这是我的第一个建议,但使用起来更少。

    【讨论】:

    • 是否有全局函数自动四舍五入所有小数字段?
    • 不,你必须告诉它你想要的东西有多精确。你在什么情况下无法事先控制输入?
    • 好的,经过大量的代码研究,如果您直接分配给模型实例,最新版本的 Django 应该会为您执行此操作 - 您使用的是哪个版本?您是保存模型实例,还是保存表单?
    • django 1.8。有时我会使用其中任何一种。
    【解决方案3】:

    如果您想设置小部件以限制十进制数字输入表单仅使用此代码呈现

    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}))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-07-04
      • 2015-09-06
      • 2018-04-16
      • 1970-01-01
      • 2016-08-11
      • 1970-01-01
      • 1970-01-01
      • 2016-05-21
      相关资源
      最近更新 更多