添加对在 django 模型和表单中使用货币和货币字段的支持。使用 py-moneyed 作为货币实现。
项目描述
一个小 Django 应用程序,它使用py-moneyed在模型和表单中添加对 Money 字段的支持。
支持的 Django 版本:2.2、3.2、4.0、4.1
支持的 Python 版本:3.7、3.8、3.9、3.10
支持的 PyPy 版本:PyPy3
如果您需要对旧版本 Django 和 Python 的支持,请参阅发行说明中提到的旧版本。
通过依赖py-moneyed,django-money得到:
支持正确的 Money 值处理(使用标准 Money 设计模式)
流通中所有货币的货币类别和定义
使用正确的货币符号格式化大多数货币
安装
使用点子:
$ pip install django-money
这会自动安装py-moneyed v1.2(或更高版本)。
将djmoney添加到您的INSTALLED_APPS中。这是必需的,以便在管理员中正确显示货币字段。
INSTALLED_APPS = [
...,
'djmoney',
...
]
模型使用
用作普通模型字段:
from djmoney.models.fields import MoneyField
from django.db import models
class BankAccount(models.Model):
balance = MoneyField(max_digits=14, decimal_places=2, default_currency='USD')
为了遵守某些严格的会计或财务法规,您可以考虑使用max_digits=19和decimal_places =4,请参阅此StackOverflow 答案中的更多信息
也可以有一个可为空的MoneyField:
class BankAccount(models.Model):
money = MoneyField(max_digits=10, decimal_places=2, null=True, default_currency=None)
account = BankAccount.objects.create()
assert account.money is None
assert account.money_currency is None
搜索带有货币字段的模型:
from djmoney.money import Money
account = BankAccount.objects.create(balance=Money(10, 'USD'))
swissAccount = BankAccount.objects.create(balance=Money(10, 'CHF'))
BankAccount.objects.filter(balance__gt=Money(1, 'USD'))
# Returns the "account" object
默认货币代码长度为3,但您可以使用CURRENCY_CODE_MAX_LENGTH设置更改它。
注意:此设置也会影响交换插件的初始迁移,因此在运行初始迁移后更改它没有效果。(如果您想更改它,您需要管理迁移交换零并再次迁移)。
现场验证
字段验证有 3 种不同的可能性:
尽管有货币,但按货币的数字部分;
按单笔金额;
由多个金额。
它们都可以组合使用,如下所示:
from django.db import models
from djmoney.models.fields import MoneyField
from djmoney.money import Money
from djmoney.models.validators import MaxMoneyValidator, MinMoneyValidator
class BankAccount(models.Model):
balance = MoneyField(
max_digits=10,
decimal_places=2,
validators=[
MinMoneyValidator(10),
MaxMoneyValidator(1500),
MinMoneyValidator(Money(500, 'NOK')),
MaxMoneyValidator(Money(900, 'NOK')),
MinMoneyValidator({'EUR': 100, 'USD': 50}),
MaxMoneyValidator({'EUR': 1000, 'USD': 500}),
]
)
上述模型中的balance字段具有以下验证:
所有输入值应在 10 到 1500 之间,尽管使用货币;
挪威克朗金额 (NOK) 应在 500 到 900 之间;
欧元应该在100到1000之间;
美元应该在50到500之间;
添加新货币
货币列在 moneyed 上,此模块使用它在管理员上提供选择列表,也用于验证。
要为所有项目添加可用的新货币,您只需将以下几行添加到您的settings.py文件中:
import moneyed
BOB = moneyed.add_currency(
code='BOB',
numeric='068',
name='Peso boliviano',
countries=('BOLIVIA', )
)
要限制项目中列出的货币,请在 settings.py 上设置一个带有货币代码列表的CURRENCIES 变量
CURRENCIES = ('USD', 'BOB')
该列表必须包含有效的货币代码
此外,还可以直接指定货币选择:
CURRENCIES = ('USD', 'EUR')
CURRENCY_CHOICES = [('USD', 'USD $'), ('EUR', 'EUR €')]
关于模型管理器的重要说明
Django-money 让您可以为模型使用任何您喜欢的自定义模型管理器,但它需要包装一些方法以允许搜索具有货币值的模型。
对于使用 MoneyField 的任何模型中的“对象”属性,这是自动完成的。但是,如果将管理器分配给其他属性,则必须手动包装管理器,如下所示:
from djmoney.models.managers import money_manager
class BankAccount(models.Model):
balance = MoneyField(max_digits=10, decimal_places=2, default_currency='USD')
accounts = money_manager(MyCustomManager())
此外,money_manager 包装器仅包装标准 QuerySet 方法。如果你定义了自定义的 QuerySet 方法,最终不使用任何标准方法(如“get”、“filter”等),那么你还需要手动装饰这些自定义方法,如下所示:
from djmoney.models.managers import understands_money
class MyCustomQuerySet(QuerySet):
@understands_money
def my_custom_method(*args, **kwargs):
# Awesome stuff
格式本地化
如果您在设置文件中设置了USE_L10N = True ,则会打开格式设置。
如果在配置中禁用格式化,则在模板中将使用默认格式。
在模板中,您可以使用特殊标签来格式化货币。
在文件settings.py中,从库djmoney添加到INSTALLED_APPS条目:
INSTALLED_APPS += ('djmoney', )
在模板中,添加:
{% load djmoney %} ... {% money_localize money %}
仅此而已。
对标签money_localize的说明:
{% money_localize <money_object> [ on(default) | off ] [as var_name] %} {% money_localize <amount> <currency> [ on(default) | off ] [as var_name] %}
例子:
同样的效果:
{% money_localize money_object %} {% money_localize money_object on %}
赋值给变量:
{% money_localize money_object on as NEW_MONEY_OBJECT %}
用货币格式化数字:
{% money_localize '4.5' 'USD' %}
Return:: Money object
测试
安装所需的软件包:
git clone https://github.com/django-money/django-money cd ./django-money/ pip install -e ".[test]" # installation with required packages for testing
运行测试的推荐方法:
tox
在当前环境python中测试应用程序:
make test
使用汇率
要使用汇率,请将以下内容添加到您的INSTALLED_APPS中。
INSTALLED_APPS = [
...,
'djmoney.contrib.exchange',
]
此外,还需要安装证书。可以通过安装djmoney和额外的交换来完成:
pip install "django-money[exchange]"
要创建所需的关系,请运行python manage.py migrate。要使用数据填充这些关系,您需要选择一个数据源。目前,支持 2 个数据源 - https://openexchangerates.org/(默认)和https://fixer.io/。要选择另一个数据源,将带有可导入字符串的EXCHANGE_BACKEND设置设置到后端,您需要:
EXCHANGE_BACKEND = 'djmoney.contrib.exchange.backends.FixerBackend'
如果要实现自己的后端,则需要扩展djmoney.contrib.exchange.backends.base.BaseExchangeBackend。上面提到的两个数据源都没有开放,所以你必须指定访问密钥才能使用它们:
OPEN_EXCHANGE_RATES_APP_ID - '<您来自 openexchangerates.org 的实际密钥>'
FIXER_ACCESS_KEY - '<您来自 fixer.io 的实际密钥>'
基础货币的后端回报率,默认为美元,但可以通过BASE_CURRENCY设置进行更改。Open Exchanger Rates & Fixer 支持一些额外的东西,例如历史数据或限制货币以响应特定列表。为了使用这些功能,您可以更改这些后端的默认 URL:
OPEN_EXCHANGE_RATES_URL = 'https://openexchangerates.org/api/historical/2017-01-01.json?symbols=EUR,NOK,SEK,CZK'
FIXER_URL = 'http://data.fixer.io/api/2013-12-24?symbols=EUR,NOK,SEK,CZK'
或者,您可以将其直接传递给update_rates方法:
>>> from djmoney.contrib.exchange.backends import OpenExchangeRatesBackend
>>> backend = OpenExchangeRatesBackend(url='https://openexchangerates.org/api/historical/2017-01-01.json')
>>> backend.update_rates(symbols='EUR,NOK,SEK,CZK')
有可能同时使用多个后端:
>>> from djmoney.contrib.exchange.backends import FixerBackend, OpenExchangeRatesBackend
>>> from djmoney.contrib.exchange.models import get_rate
>>> OpenExchangeRatesBackend().update_rates()
>>> FixerBackend().update_rates()
>>> get_rate('USD', 'EUR', backend=OpenExchangeRatesBackend.name)
>>> get_rate('USD', 'EUR', backend=FixerBackend.name)
Money的常规操作将使用EXCHANGE_BACKEND后端来获取费率。此外,还有两个管理命令可用于更新和删除费率:
$ python manage.py update_rates
Successfully updated rates from openexchangerates.org
$ python manage.py clear_rates
Successfully cleared rates for openexchangerates.org
它们都接受-b/--backend选项,它将仅更新/清除此后端的数据。并且clear_rates接受-a/--all选项,这将清除所有后端的数据。
要设置定期费率更新,您可以使用 Celery 任务:
CELERYBEAT_SCHEDULE = {
'update_rates': {
'task': 'path.to.your.task',
'schedule': crontab(minute=0, hour=0),
'kwargs': {} # For custom arguments
}
}
示例任务实现:
from django.utils.module_loading import import_string
from celery import Celery
from djmoney import settings
app = Celery('tasks', broker='pyamqp://guest@localhost//')
@app.task
def update_rates(backend=settings.EXCHANGE_BACKEND, **kwargs):
backend = import_string(backend)()
backend.update_rates(**kwargs)
要将一种货币转换为另一种货币:
>>> from djmoney.money import Money
>>> from djmoney.contrib.exchange.models import convert_money
>>> convert_money(Money(100, 'EUR'), 'USD')
<Money: 122.8184375038380800 USD>
汇率与 Django Admin 集成。
通过在 Django 设置中设置AUTO_CONVERT_MONEY = True可以将 django-money 配置为自动使用此应用进行货币转换。请注意,货币转换是一个有损过程,因此自动转换通常仅适用于非常简单的用例。对于大多数用例,您需要明确何时发生货币转换,并且自动转换可以隐藏错误。此外,通过自动转换,由于发生在不同方向的转换,您会丢失一些属性,例如交换性 ( A + B == B + A )。
与 Django REST 框架一起使用
确保djmoney位于settings.py的INSTALLED_APPS中 ,并且已经安装了rest_framework 。MoneyField 将通过djmoney.apps.MoneyConfig.ready()自动为 Django REST Framework 注册一个序列化程序 。
您可以通过以下方式添加可序列化字段:
from djmoney.contrib.django_rest_framework import MoneyField
class Serializers(serializers.Serializer):
my_computed_prop = MoneyField(max_digits=10, decimal_places=2)
内置序列化程序的工作方式如下:
class Expenses(models.Model):
amount = MoneyField(max_digits=10, decimal_places=2)
class Serializer(serializers.ModelSerializer):
class Meta:
model = Expenses
fields = '__all__'
>>> instance = Expenses.objects.create(amount=Money(10, 'EUR'))
>>> serializer = Serializer(instance=instance)
>>> serializer.data
ReturnDict([
('id', 1),
('amount_currency', 'EUR'),
('amount', '10.000'),
])
请注意,在您的序列化程序上指定单个字段时,金额和货币字段是分开处理的。要实现与上述相同的行为,您将包括两个字段名称:
class Serializer(serializers.ModelSerializer):
class Meta:
model = Expenses
fields = ('id', 'amount', 'amount_currency')
定制
如果需要自定义将Money实例解构到 Django 字段上的过程,或者反过来,则可以使用像这样的自定义描述符:
class MyMoneyDescriptor:
def __get__(self, obj, type=None):
amount = obj.__dict__[self.field.name]
return Money(amount, "EUR")
当调用 obj.money时,它将始终对所有Money实例使用EUR。然后它应该被传递给MoneyField:
class Expenses(models.Model):
amount = MoneyField(max_digits=10, decimal_places=2, money_descriptor_class=MyMoneyDescriptor)
背景
该项目是 http://code.google.com/p/python-money/中的 Django 支持的一个分支
这个版本增加了测试,并带有几个关键的错误修正。