【问题标题】:Django percentage fieldDjango 百分比字段
【发布时间】:2016-04-07 13:31:56
【问题描述】:

我正在尝试在 Django 中创建一个百分比字段,用户只需填写 40 即可获得 40%。输入框右侧会有一个百分号,让他们知道应该填写一个百分比。 0.4 必须存储在数据库中。到目前为止,我已经尝试了以下方法:

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,而不会除以它。

有什么建议可以解决吗?

我已经尝试过this 解决方案,但它会重复该值 100 次。改正后也有同样的问题。

我使用的是 Python3.5

【问题讨论】:

  • 如果您将40 视为40% 并将0.4 视为0.4%,那么为什么要使用单独的字段?为什么不使用带有最小值和最大值验证器的简单 FloatField()?
  • 用户可以填写120,也可以填写120%。如果他们填1.2,我不知道是不是1.2%的120%
  • .. 那么,您希望用户能够为 120% 填充 120?
  • 为什么不保存提交的值,然后在向用户显示时转换为浮动?
  • @ahmed:我确实考虑过这一点,但是这些数字是用于计算的,所以我不想记住每次在使用数字之前都要除以 100。对我来说,用 40 表示 40% 似乎更加用户友好,所以我觉得奇怪的是没有一个简单的解决方案可用。

标签: python django


【解决方案1】:

这个任务有一个简单的替代方法。您可以为此使用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)

【讨论】:

    【解决方案2】:

    我找到了解决方案。我必须检查传入的值是否是字符串。如果是,我不会乘以 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
    

    【讨论】:

      【解决方案3】:

      根据文档,to_python() 应该用于处理复杂数据类型的情况,以帮助您与数据库进行交互。我认为更准确的方法是覆盖 pre_save() Field 方法。来自文档:

      pre_save(model_instance, add)

      在 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 之间的值,这就是我需要验证器的原因,但想法就在这里。

      【讨论】:

        【解决方案4】:

        我的解决方案

        基于@elachere 答案和Django documentation,这是我使用的代码:

        from decimal import Decimal
        
        from django import forms
        from django.db import models
        
        
        class PercentageField(models.DecimalField):
            def from_db_value(self, value, expression, connection):
                if value is None:
                    return value
                return Decimal(value) * Decimal("100")
        
            def pre_save(self, model_instance, add):
                value = super().pre_save(model_instance, add)
                if value is not None:
                    setattr(model_instance, self.attname, Decimal(value) * Decimal(".01"))
                    return Decimal(value) * Decimal(".01")
                return None
        

        为什么接受的答案对我不起作用

        我遇到了类似的问题,但我希望我的PercentageField 基于DecimalField 而不是FloatField,在货币方面与recommendations 一致。在这种情况下,当前接受的答案不适用于 Django 4.0,原因有两个:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-03-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-11-04
          • 1970-01-01
          相关资源
          最近更新 更多