Как представить денежное поле Django с символом валюты в шаблоне списка и простым десятичным числом для редактирования?

Когда я отображаю деньги в шаблоне списка или подробном шаблоне (только для чтения), я хотел бы иметь возможность отображать красивую версию значения поля, которая включает символ валюты: «100,00 долларов США».

Однако, когда запись редактируется, я хотел бы отобразить «100,00» в поле TextInput без какого-либо символа валюты.

Я создал собственный класс models.DecimalField и класс forms.DecimalField для представления денег в моем приложении Django.

Поэтому я пытаюсь придумать способ иметь альтернативную версию значения поля в шаблоне, которая будет красивой (т.е. правильно отформатирована для просмотра, но не идеальна для редактирования). Я знаю, что есть фильтры шаблонов, которые позволяют вам это сделать, но я считаю, что это помешает мне перебирать поля в форме и отображать их значения.

Если я смогу добавить красивый атрибут в свой класс пользовательского поля, я смогу проверить, существует ли он в шаблоне, и отобразить его. До сих пор я пробовал это:

class MoneyFormField(forms.DecimalField):
    def __init__(self, *args, **kwargs):
        # I plan to add symbol localization code here
        self.symbol= '$'

    def prepare_value(self, value):
        # assign pretty value to an attribute that template can access
        self.pretty = self.pretty_value(value)            
        try:
            return round(Decimal(value),2)
        except:
            return 0

    def pretty_value(self, value):
        if not value:
            value = 0
        return self.symbol + str(round(Decimal(value),2))

Проблема в том, что для доступа к красивому значению в шаблоне я должен сначала получить доступ к исходному значению, чтобы вызвать prepare_value().

Если я попытаюсь напечатать красивое значение в таком шаблоне...

{% for field in payment_form %}
    {{ field.field.pretty }} {{ field.value }} {{ field.field.pretty }} 
{% endfor %}

тогда первая картинка игнорируется, но печатается последняя картинка.

Если бы я мог заставить это работать, я бы отобразил красивое значение (или значение по умолчанию, если красивого не существует) в таком шаблоне:

{% if field.field.pretty %}
    {{ field.field.pretty}}
{% else %}
    {{ field.value }}
{% endif %}

Есть ли у кого-нибудь предложения по созданию альтернативного представления поля, доступного для шаблона?

TL;DR- Как я могу получить доступ к значению поля в init поля формы?


person mcfreeze    schedule 13.02.2017    source источник


Ответы (2)


Я придумал решение, но, поскольку я новичок в Django и python, я могу излишне усложнять вещи. Так что буду рад любым отзывам!

Подводя итог: я хотел, чтобы мои денежные поля имели «красивую» версию, которая могла бы отображать деньги как «100,00 долларов США» вместо «100,00» в определенных шаблонах, таких как списки и детали только для чтения.

Я создал поле денежной модели, в котором указано мое пользовательское поле формы MoneyFormField:

class MoneyField(models.DecimalField):
    def __init__(self, *args, **kwargs):
        kwargs['max_digits'] = 13
        kwargs['decimal_places'] = 4
        kwargs['default'] = 0

    def formfield(self, **kwargs):
        defaults = {'form_class': MoneyFormField}
        defaults.update(kwargs)
        return super(MoneyField, self).formfield(**defaults)        

Вот мое поле формы MoneyFormField. Обратите внимание на вызов «get_bound_field»:

class MoneyFormField(forms.DecimalField):
    def __init__(self, *args, **kwargs):
        self.widget = MoneyTextInput() # custom widget I'm experimenting with
        self.prefix = '$'            
        super(MoneyFormField, self).__init__(*args, **kwargs)

    def get_bound_field(self, form, field_name):
        return MoneyBoundField(form, self, field_name)        

    def prepare_value(self, value):
        try:
            return round(Decimal(value),2)
        except:
            return 0

MoneyBoundField — это то, что делает «красивую» версию поля доступной для шаблона:

class MoneyBoundField(BoundField):
    def pretty(self):
        return self.field.prefix+str(self.value())

Если бы я просто сделал это, я мог бы напечатать красивые значения, сначала проверив, существует ли красивое значение, а если нет, напечатав исходное значение следующим образом:

{% for somefield in payment_form %}
    {% if somefield.pretty %}
        {{ somefield.pretty }}
    {% else %}
        {{ somefield.value }}
    {% endif %}    
{% endfor %}

Но я бы предпочел просто сделать что-то вроде этого:

{% for somefield in payment_form %}
    {{ somefield.pretty }}
{% endfor %}

Поэтому мне пришлось добавить в основной класс Django BoundField вboundfields.py красивую часть, добавив две строки:

boundfield.py
class BoundField(object):
...
    def pretty(self):
        # return default value.  Let descendant classes override this function where desired
        return self.value()
person mcfreeze    schedule 15.02.2017

Вот еще один подход, который я использовал. Вместо того, чтобы возиться с основным определением класса django BoundField вboundfield.py, я теперь просто использую фильтр шаблона, чтобы сделать поле красивым. Фильтр делает следующее:

  • Если поле является полем выбора, оно преобразует значение индекса выбора в строку описания.
  • Если поле имеет настраиваемое поле формы с настраиваемым BoundField и функцией с именем «pretty», оно вызывает эту функцию и возвращает значение.
  • (закомментировано) вы можете попытаться вызвать определенный метод в своем пользовательском классе поля формы, если это применимо. Это устраняет необходимость определять BoundField в вашем классе formfield.
  • Если они не применяются, он просто возвращает значение поля без изменений.

Это код в моем app_filters.py:

from django.forms.fields import ChoiceField

@register.filter
def pretty_filter(field):
    # is this a better way to get the field's value?
    # field_value = field.form.initial[field.name]

    # get the field's value (for choice fields, this is the integer index used to look up the verbose string in the choices dict)
    field_value = field.value()
    if field_value is None:
        return ''
    # the the field has 'choices' then return the verbose string for the choice
    if isinstance(field.field, ChoiceField): 
        for (choices_index, description) in field.field.choices: 
            if choices_index == field_value: 
                return description
        return '' # no match found

    # # if your model.field's formfield has a function called
    # # another_custom_function() you can call it this way:
    # try:
    #     return field.field.another_custom_function()
    # except:
    #     pass

    # attempt to return pretty value if field has th
    try:
        return field.pretty()
    except:
    # else return default value
        return field_value

Я также внес изменения в красивую функцию своего пользовательского денежного поля, добавив запятую (это можно изменить для обработки локализации):

class MoneyBoundField(BoundField):    
    def pretty(self):
        # return $12,345.67
        return "{}{:,.2f}".format(self.field.prefix,self.value())

class MoneyFormField(forms.DecimalField):
    def __init__(self, *args, **kwargs):
        self.widget = MoneyTextInput()
        self.prefix = '$'
        super(MoneyFormField, self).__init__(*args, **kwargs)

    def get_bound_field(self, form, field_name):
        return MoneyBoundField(form, self, field_name)    

Затем в моем шаблоне я использовал это так:

{% load app_filters %}

{% for item in payment_form %}
    {{item|pretty_filter}}
{% endfor %}                   

Большая часть кода фильтра скопирована у пользователя Simple10 здесь: https://code.djangoproject.com/ticket/10427#comment:18

Пожалуйста, поделитесь любыми мыслями или комментариями!

person mcfreeze    schedule 16.02.2017