6

Django プロジェクトにさまざまな測定システムを実装して、ユーザーがメートル法または帝国単位を使用するかどうかを選択できるようにしたいと考えています。ただし、これを行うための正しいアプローチが何であるかはわかりません。

現在、私のモデルには測定単位を認識するフィールド (整数/小数フィールド) がありません。フィールドを直接変更する必要はありません。

私の現在のデータベース値はすでにメトリック値を表しているため、そのままにしておく予定です。つまり、ユーザー入力/値出力の変換を処理する必要があります。Pintは、この目的に最適なライブラリのようです。

これは、アプリでフィールドを編集せずに、代わりに登録パターンなどを使用して可能ですか? 私が達成したいのは、次のようなものです。

  1. 長さなど、さまざまな測定タイプと可能な値を保持するデータ構造を定義します。メートル、フィート。重量: キログラム、ポンドなど
  2. 新しいファイル「measurements.py」などを各アプリ ディレクトリに追加します。このファイルには、測定値を保持するフィールドがあります。このフィールドでは、フィールド = {mymodelfield: length, myothermodelfield: weight} など、正確な測定フィールドとそのタイプを定義できます。
  3. 各測定の既定の単位 (データベースに保存されている単位) など、いくつかの既定の設定を設定ファイルで設定し、アプリ ファイルで上書きすることができます。
  4. ユーザーは、測定タイプごとに好みを設定します。前のポイントで述べたように、測定タイプごとにデフォルトの単位があります。設定/デフォルト (保存) 単位が一致しない場合にユーザー入力を変換するロジックが必要です。バインドされたフォームは、フィールド値をデフォルトからユーザーの好みの単位に戻すこともできる必要があります。

この種のソリューションは、フィールドやフォームがアプリで直接変更されないため、既存のアプリへのプラグインを容易にします。登録パターンを使用した基本的な例が役立ちます。

Django 以外のプロジェクトでこれを行う方法の一般的なアイデアやパターンなど、関連する情報を歓迎します。

4

1 に答える 1

3

引き続きメトリックに保存したいので、変換する可能性があるのは次の場合だけです。

  1. ユーザー入力
  2. ユーザーへのデータの表示

ユーザー入力には、メトリック値を出力するカスタム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を使用してフォームをインスタンス化すると、 inのメトリックのデフォルト設定TestFormが上書きされる可能性があるinitial={'num':[None,'imp']}ため、プロファイルからユーザー設定を挿入できます。

上記のフォームを使用するとis_valid()、フォームが返すデータのフォームがメトリックになることがわかります。

ユーザーがフォームを見ると、Django NumberInputとして表示され、その後に既定の設定が既に選択されている選択が続きます。

データを表示するには、テンプレート フィルターを使用できます。これはあなたにアイデアを与えるための簡単なものです

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}}

もちろん、これはすべてマイル/キロメートルを変換するだけです。ウィジェット/フィールドを複製して変更またはサブクラス化する必要があり、おそらくポンド/キログラムなどの他のものをカバーするためにいくつかのテンプレート フィルターが必要です。

于 2014-04-30T14:44:11.377 に答える