Solución:
Recibes el error principalmente porque forms.DecimalField
tiene validadores separados de 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 {}
y forms.DecimalField
se usa por defecto para formularios para modelos con campos de clase models.DecimalField
. Podrías hacer algo como esto:
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)
Ahora en cualquier lugar que estés usando models.DecimalField
, usar RoundingDecimalModelField
en lugar de. Cualquier formulario que use con esos modelos ahora también usará el campo de formulario personalizado.
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')}
Si está asignando directamente a una instancia de modelo, no necesita preocuparse por ello. El objeto de campo cuantificará el valor (redondeándolo) al nivel de punto decimal que estableció en la definición de su modelo.
Si se trata de un ModelForm
, el valor por defecto DecimalField
requerirá que cualquier entrada coincida con los puntos decimales del campo del modelo. La forma más fácil de manejar esto en general es probablemente subclasificar el modelo DecimalField
, eliminando el validador específico decimal y confiando en la conversión subyacente para cuantificar los datos, con algo como esto:
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)
Luego en tus modelos:
amount = RoundingDecimalField(max_digits = 19, decimal_places = 2)
(En realidad, no coloque la clase de campo en el mismo campo que el modelo, eso es solo por ejemplo).
Esto probablemente sea menos correcto en términos absolutos que definir un formulario de campo personalizado, que fue mi primera sugerencia, pero es menos complicado de usar.