Django百分比字段

ygya80vv  于 2022-12-01  发布在  Go
关注(0)|答案(4)|浏览(183)

我尝试在Django中创建一个百分比字段,用户只需填写40表示40%。在输入框的右边会有一个百分比符号,这样他们就知道他们应该填写一个百分比。0.4必须存储在DB中。到目前为止,我已经尝试了以下方法:

class PercentageField(fields.FloatField):
    widget = fields.TextInput(attrs={"class": "percentInput"})

    def to_python(self, value):
        val = super(PercentageField, self).to_python(value)
        if is_number(val):
            return val/100
        return val

    def prepare_value(self, value):
        val = super(PercentageField, self).prepare_value(value)
        if is_number(val):
            return str((float(val)*100))
        return val

def is_number(s):
    if s is None:
        return False
    try:
        float(s)
        return True
    except ValueError:
        return False

它可以工作,但问题是,当我发布无效数据并再次呈现表单时,它将40显示为4000。换句话说,它将数字再次乘以100,而不是除以100。
有什么建议吗?
我试过this解决方案,但是它重复了100次这个值。在我纠正这个问题后,它也有同样的问题。
我用的是Python 3.5

xxe27gdn

xxe27gdn1#

我找到了解决方案。我必须检查传入的值是否是字符串。如果是,我就不乘以100,因为它来自表单。见下图:

class PercentageField(fields.FloatField):
    widget = fields.TextInput(attrs={"class": "percentInput"})

    def to_python(self, value):
        val = super(PercentageField, self).to_python(value)
        if is_number(val):
            return val/100
        return val

    def prepare_value(self, value):
        val = super(PercentageField, self).prepare_value(value)
        if is_number(val) and not isinstance(val, str):
            return str((float(val)*100))
        return val
ee7vknir

ee7vknir2#

有一个简单的替代方法,你可以使用MaxValueValidatorMinValueValidator
以下是您可以执行此操作的方法:

from django.db import models    
from django.core.validators import MinValueValidator, MaxValueValidator
        
PERCENTAGE_VALIDATOR = [MinValueValidator(0), MaxValueValidator(100)]
        
class RatingModel(models.Model):
    ...
    rate_field = models.DecimalField(max_digits=3, decimal_places=0, default=Decimal(0), validators=PERCENTAGE_VALIDATOR)
nnsrf1az

nnsrf1az3#

我的解决方案

根据@elachere的回答和Django文档,这是我正在使用的代码:

from decimal import Decimal

from django import forms
from django.db import models

def ft_strip(d: Decimal) -> Decimal:
    return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize()

class PercentageField(models.DecimalField):
    def from_db_value(self, value, expression, connection) -> Decimal | None:
        return value if value is None else ft_strip(Decimal(str(value)) * 100)

    def get_db_prep_save(self, value, connection):
        if value is not None:
            value = Decimal(str(value)) / 100
        return super(PercentageField, self).get_db_prep_save(value, connection)

为什么接受的答案对我无效

我遇到了类似的问题,但我希望我的PercentageField是基于DecimalField而不是FloatField,在货币方面与recommendations一致。在这种情况下,目前公认的答案对我在Django 4.0中不起作用,原因有2:

  • to_python被调用了两次,一次是由clean方法调用(如the documentation中所述),另一次是由get_db_prep_save调用(在文档中提到)。事实上,(在Django源代码中)DecimalFieldFloatField在这一点上是不同的。
  • 对于基于现有示例(例如,用户可能愿意编辑的示例)的表单,prepare_value根本不会运行。

覆盖django.db.models.fields.Field.pre_save可能是一种替代方法,但以下代码仍然存在问题:刚刚保存的当前示例的属性太小了100倍(由于pre_save中的除法),如果需要进一步使用它,则必须调用instance.refresh_from_db()

class PercentageField(models.DecimalField):
    def from_db_value(self, value, expression, connection) -> Decimal | None:
        return value if value is None else ft_strip(Decimal(str(value)) * 100)

    def pre_save(self, model_instance, add):
        value = super().pre_save(model_instance, add)
        if value is not None:
            updated_value = Decimal(str(value)) / 100
            setattr(model_instance, self.attname, updated_value)
            return updated_value
        return None
kxxlusnw

kxxlusnw4#

从文档中可以看出,to_python()应该用于处理复杂数据类型的情况,以帮助您与数据库交互。我认为更准确的方法是覆盖pre_save()Field方法。从文档中可以看出:
pre_save(模型示例,添加)
在get_db_prep_save()之前调用的方法,用于在保存值之前准备值(例如,用于DateField.auto_now)。
最后,它看起来像这样:

def validate_ratio(value):
    try:
        if not (0 <= value <= 100):
            raise ValidationError(
                f'{value} must be between 0 and 100', params={'value': value}
            )
    except TypeError:
        raise ValidationError(
            f'{value} must be a number', params={'value': value}
        )

class RatioField(FloatField):
    description = 'A ratio field to represent a percentage value as a float'

    def __init__(self, *args, **kwargs):
        kwargs['validators'] = [validate_ratio]
        super().__init__(*args, **kwargs)

    def pre_save(self, model_instance, add):
        value = getattr(model_instance, self.attname)
        if value > 1:
            value /= 100
        setattr(model_instance, self.attname, value)
        return value

我的情况有点不同,我想要一个比率而不是一个百分比,所以我只允许0到100之间的值,这就是为什么我需要一个验证器,但想法是在这里。

相关问题