Django项目中不同测量系统的概念

时间:2014-04-30 08:49:56

标签: python django units-of-measurement unit-conversion

我想在我的Django项目中实现不同的测量系统,以便用户可以选择使用公制或英制单位。但是,我不知道这样做的正确方法是什么。

目前我的模型没有测量单位识别字段(它们是整数/小数字段),我希望我不必直接更改字段。

由于我当前的数据库值已经表示度量值,我计划保持这种方式,这意味着我将不得不处理用户输入/值输出转换。 Pint似乎是一个很好的图书馆。

这是否可以在不修改应用中的字段的情况下使用registration patterns或其他内容?我想要实现的是这样的:

  1. 我定义了一个包含不同测量类型和可能值的数据结构,例如长度:米,英尺;体重:千克,磅等。
  2. 我添加了一个新文件" measurements.py"或每个app目录中的类似内容,其中包含保存测量值的字段。在这个字段中,我可以定义哪些是精确的测量字段以及它们的类型,例如fields = {mymodelfield:length,myothermodelfield:weight}等。
  3. 可以在设置文件中设置一些默认设置,并在app文件中覆盖,例如每个测量的默认单位(存储在数据库中的单位)。
  4. 用户为每种测量类型设置他的偏好。每个测量类型都有默认单位,如前一点所述。在偏好/默认(存储)单元不匹配的情况下转换用户输入的逻辑是必需的。绑定表单还应该能够将字段值从默认值转换回用户首选单位。
  5. 这种解决方案可以更轻松地将其插入现有应用,因为在应用中不会直接更改任何字段和表单。使用注册模式的基本示例会很有帮助。

    欢迎提供任何相关信息,包括在非Django项目中如何完成此操作的一般想法和模式。

1 个答案:

答案 0 :(得分:3)

由于您希望继续以指标存储,因此您可能转换的唯一时间是:

  1. 用户输入
  2. 向用户显示数据
  3. 对于用户输入,您可以使用自定义MultiValueField和自定义MultiWidget来输出指标值。

    from django import forms 
    from django.forms import widgets,Form, CharField, MultiValueField, ChoiceField, DecimalField
    
    imperial = "imp"
    imperial_display = "Miles"
    metric = "met"
    metric_display  = "Kilometers"
    unit_types = ((imperial, imperial_display), (metric, metric_display))
    
    #You would use that library instead of this, and maybe not floats
    def to_imperial(val):
        return float(val) * 1.60934
    
    def to_metric(val):
        return float(val) / 0.62137
    
    class NumberUnitWidget(widgets.MultiWidget):
        def __init__(self, default_unit=None, attrs=None):
            #I feel like this default_unit thing I'm doing is ugly
            #but I couldn't think of another way to get the decompress
            #to know which unit to convert to
            self.default_unit = default_unit
    
            _widgets = [
                widgets.NumberInput(attrs=attrs),
                widgets.Select(attrs=attrs, choices=unit_types),
            ]
            super(NumberUnitWidget, self).__init__(_widgets, attrs)
    
        #convert a single value to list for the form
        def decompress(self, value):
            if value:
                if self.default_unit == imperial:
                    return [to_imperial(value), self.default_unit]
    
                #value is the correct format
                return [value, self.default_unit]
            return [0, self.default_unit]
    
    class NumberUnitField(MultiValueField):
    
        def __init__(self, *args, **kwargs):
            _widget = NumberUnitWidget(default_unit=kwargs['initial'][0])
            fields = [
                DecimalField(label="Number!"),
                ChoiceField(choices=unit_types)
            ]
            super(NumberUnitField, self).__init__(fields=fields, widget = _widget, *args, **kwargs)
    
    
        def compress(self, values):
            if values[1] == imperial:
                #They inputed using imperial, convert to kilo
                return  to_metric(values[0])
            return values[0] #You dont need to convert because its in the format you want
    
    class TestForm(Form):
        name = CharField()
        num = NumberUnitField(initial=[None, metric])
    

    使用num实例化表单时,可以覆盖TestForminitial={'num':[None,'imp']}的度量标准的默认设置,以便您可以从其个人资料中插入用户首选项。

    如果您看到表单is_valid()表单将返回给您的数据,请使用上面的表单。

    当用户看到表单时,它将显示为Django NumberInput,然后是已选择默认设置的选择。

    要显示数据,您可以使用template filter。这是一个简单的给你一个想法

    from django import template
    register = template.Library()
    @register.filter
    def convert_units(value, user_pref):
        #You probably want to use that library you found here to do conversions
        #instead of what i've used.
        if user_pref == "met":
            return str(value) + " Kilometers"
        else:
            return str(float(value)/ 1.60934) + " Miles"
    

    然后在模板中它看起来像:

    {{a_value|convert_units:user_pref}}
    

    当然所有这些只是转换里程/公里数。您必须复制并更改或子类化窗口小部件/字段,并且可能还有一些模板过滤器来覆盖其他内容,例如磅/公斤。