【问题标题】:Customizing how a form outputs a field to a widget in django自定义表单如何将字段输出到 django 中的小部件
【发布时间】:2009-08-27 07:02:43
【问题描述】:

我有一个名为 HourField 的自定义字段,可让您输入“1:30”来表示一个半小时。 clean() 方法将模型中的 FloatField 转换为 1.5。这一切都很好。现在我遇到了一个问题,当我想创建一个表单集时,该字段被呈现为“1.5”而不是我想要的“1:30”。

是否有某种类似于clean() 的方法,但反之亦然?

编辑:使用另一种存储方法是不可能的。我制作了一些其他自定义字段,它们存储为一种类型的对象,但由用户作为字符串输入。我使用上面的例子是因为它是最直接的。

【问题讨论】:

    标签: django django-forms


    【解决方案1】:

    要自定义字段的呈现方式,您还需要定义一个小部件。

    以下代码显示了 django 中小部件的一些职责:

    • 渲染表单标签(超类 TextInput 负责这个)
    • 格式化在表单域内显示的值。 这里有一个陷阱:值的类可能会有所不同,因为您可能会呈现来自字段的已清理值,或者来自表单 POST 的未清理数据。因此测试我们是否在 _format_value 中有一个数值;如果是字符串,则保持原样。
    • 与初始数据相比,判断值是否发生了变化。当您使用具有默认值的表单集时非常重要。

    请注意,小部件的代码灵感来自 django 源代码中的 widgets.TimeInput,这有助于保持一致性。

    from django import forms
    from django.forms import widgets
    from django.forms.util import ValidationError
    
    class HourWidget(widgets.TextInput):
    
        def _format_value(self, value):
            if isinstance(value, float) or isinstance(value, int):
                import math
    
                hours = math.floor(value)
                minutes = (value - hours) * 60
                value = "%d:%02d" % (hours, minutes)
    
            return value
    
        def render(self, name, value, attrs=None):
            value = self._format_value(value)
            return super(HourWidget, self).render(name, value, attrs)
    
        def _has_changed(self, initial, data):
            return super(HourWidget, self)._has_changed(self._format_value(initial), data)
    
    class HourField(forms.Field):
        widget = HourWidget
    
        def clean(self, value):
            super(HourField, self).clean(value)
    
            import re
            match = re.match("^([0-9]{1,2}):([0-9]{2})$", value)
            if not match:
                raise ValidationError("Please enter a valid hour ( ex: 12:34 )")
    
            groups = match.groups()
            hour = float(groups[0])
            minutes = float(groups[1])
    
            if minutes >= 60:
                raise ValidationError("Invalid value for minutes")
    
            return hour + minutes/60
    

    请注意,1:20 变为 1:19,这是由于使用浮点数引起的精度损失。如果您不想丢失某些精度,您可能需要更改数据类型。

    【讨论】:

    • _format_value() 中的 %1d 不会像 10、11 和 12 这样截断小时吗?
    • "1" 为'width'参数,仅用于填充,不用于截断;也就是说,字符被添加到左侧,从不删除。 % 运算符的行为类似于 sprintf (en.wikipedia.org/wiki/Printf#printf_format_placeholders)。您不能以这种方式截断数字的整数部分。您只能使用精度参数 ( "%.2f" % 0.123 == "0.12" ) 截断小数部分。可以使用字符串格式的精度参数进行截断:"%.1s" % 123 == "1"
    • 顺便说一句,虽然它没有引入错误,但我同意这个 '%1d' 是无用的,因为任何数字总是在整数部分产生至少一个字符 :-) 我写这个是因为我从“%2d:%2d”开始,发现小时部分只需要 1 个字符。
    【解决方案2】:

    数据如何在表单域中显示是小部件的职责。看起来您需要定义一个自定义小部件,该小部件实现一个 render() 方法,该方法采用十进制字段值并根据需要输出它。

    不幸的是,似乎没有任何关于创建自定义小部件的文档,但您可以查看django.forms.extras.widgets 中的代码以获取有关如何执行此操作的示例。

    【讨论】:

      【解决方案3】:
      from django.forms.widgets import TextInput
      
      class TimeInput(TextInput):
           def _format_value(self, value):
               """Convert float to time"""
               if not value:
                   return ''
               fraction, integer = math.modf(value)
               minutes = 0 if fraction == 0 else .6 / fraction
               return "%d:%02d" % (int(integer), int(minutes))
      
           def render(self, name, value, attrs=None):
              value = self._format_value(value)
              return super(TimeInput, self).render(name, value, attrs)
      

      【讨论】:

      • 应该是TextInput,而不仅仅是Text。另外,如何在 ModelChoice 字段上使用此技术?价值似乎总是无...
      • 其实我收回来了,不是没有,只是一直是对象的pk……
      • ModelChoice 字段根据该模型实例的 pk 和字符串表示生成选项。这仍然与 HourField 有关吗?如果是这样,您会更详细地说明表格设置吗?
      【解决方案4】:

      我认为您可能想尝试使用date template tag 或可能创建自己的模板标签。虽然这是创可贴——真正的问题是您应该使用 datetime 模块将该时间作为时间存储在 Python 中。这将保证它的格式正确。

      【讨论】:

      猜你喜欢
      • 2010-11-16
      • 2011-02-03
      • 2017-09-03
      • 2011-03-29
      • 2012-01-18
      • 2013-04-03
      • 2014-06-24
      • 2015-11-06
      • 2011-06-10
      相关资源
      最近更新 更多