Package: django-money
A little Django app that usespy-moneyedto add support for Money
fields in your models and forms.Django versions supported: 2.2, 3.2, 4.0, 4.1, 4.2Python versions supported: 3.7, 3.8, 3.9, 3.10, 3.11PyPy versions supported: PyPy3 (for Django <= 4.0)If you need support for older versions of Django and Python, please refer to older releases mentioned inthe release notes.Through the dependencypy-moneyed,django-moneygets:Support for proper Money value handling (using the standard Money
design pattern)A currency class and definitions for all currencies in circulationFormatting of most currencies with correct currency signInstallationUsingpip:$pipinstalldjango-moneyThis automatically installspy-moneyedv1.2 (or later).Adddjmoneyto yourINSTALLED_APPS. This is required so that money field are displayed correctly in the admin.INSTALLED_APPS=[...,'djmoney',...]Model usageUse as normal model fields:fromdjmoney.models.fieldsimportMoneyFieldfromdjango.dbimportmodelsclassBankAccount(models.Model):balance=MoneyField(max_digits=14,decimal_places=2,default_currency='USD')To comply with certain strict accounting or financial regulations, you may consider usingmax_digits=19anddecimal_places=4, see more in thisStackOverflow answerIt is also possible to have a nullableMoneyField:classBankAccount(models.Model):money=MoneyField(max_digits=10,decimal_places=2,null=True,default_currency=None)account=BankAccount.objects.create()assertaccount.moneyisNoneassertaccount.money_currencyisNoneSearching for models with money fields:fromdjmoney.moneyimportMoneyaccount=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" objectThe default currency code length is3but you can change it with theCURRENCY_CODE_MAX_LENGTHsetting.Caution: this setting also affects the initial migration of theexchangeplugin, so changing it after running
the initial migration has no effect. (You’d need tomanage migrate exchange zeroand migrate again if you want
to change it).Field validationThere are 3 different possibilities for field validation:by numeric part of money despite on currency;by single money amount;by multiple money amounts.All of them could be used in a combination as is shown below:fromdjango.dbimportmodelsfromdjmoney.models.fieldsimportMoneyFieldfromdjmoney.moneyimportMoneyfromdjmoney.models.validatorsimportMaxMoneyValidator,MinMoneyValidatorclassBankAccount(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}),])Thebalancefield from the model above has the following validation:All input values should be between 10 and 1500 despite on currency;Norwegian Crowns amount (NOK) should be between 500 and 900;Euros should be between 100 and 1000;US Dollars should be between 50 and 500;Adding a new CurrencyCurrencies are listed on moneyed, and this modules use this to provide a
choice list on the admin, also for validation.To add a new currency available on all the project, you can simply add
these few lines to yoursettings.pyfile:importmoneyedBOB=moneyed.add_currency(code='BOB',numeric='068',name='Peso boliviano',countries=('BOLIVIA',))To restrict the currencies listed on the project set aCURRENCIESvariable with a list of Currency codes onsettings.pyCURRENCIES=('USD','BOB')The list has to contain valid Currency codesAdditionally there is an ability to specify currency choices directly:CURRENCIES=('USD','EUR')CURRENCY_CHOICES=[('USD','USD $'),('EUR','EUR €')]Important note on model managersDjango-money leaves you to use any custom model managers you like for
your models, but it needs to wrap some of the methods to allow searching
for models with money values.This is done automatically for the “objects” attribute in any model that
uses MoneyField. However, if you assign managers to some other
attribute, you have to wrap your manager manually, like so:fromdjmoney.models.managersimportmoney_managerclassBankAccount(models.Model):balance=MoneyField(max_digits=10,decimal_places=2,default_currency='USD')accounts=money_manager(MyCustomManager())Also, the money_manager wrapper only wraps the standard QuerySet
methods. If you define custom QuerySet methods, that do not end up using
any of the standard ones (like “get”, “filter” and so on), then you also
need to manually decorate those custom methods, like so:fromdjmoney.models.managersimportunderstands_moneyclassMyCustomQuerySet(QuerySet):@understands_moneydefmy_custom_method(*args,**kwargs):# Awesome stuffNote on serializationDjango-money provides a custom deserializer, it is not registered
by default so you will have to actively register it in yoursettings.py.SERIALIZATION_MODULES={"json":"djmoney.serializers"}Format localizationThe formatting is turned on if you have setUSE_L10N = Truein the
your settings file.If formatting is disabled in the configuration, then in the templates
will be used default formatting.In the templates you can use a special tag to format the money.In the filesettings.pyadd toINSTALLED_APPSentry from the
librarydjmoney:INSTALLED_APPS+=('djmoney',)In the template, add:{% load djmoney %}
...
{% money_localize money %}and that is all.Instructions to the tagmoney_localize:{% money_localize <money_object> [ on(default) | off ] [as var_name] %}
{% money_localize <amount> <currency> [ on(default) | off ] [as var_name] %}Examples:The same effect:{% money_localize money_object %}
{% money_localize money_object on %}Assignment to a variable:{% money_localize money_object on as NEW_MONEY_OBJECT %}Formatting the number with currency:{% money_localize '4.5' 'USD' %}Return::

    Money objectTestingInstall the required packages:git clone https://github.com/django-money/django-money

cd ./django-money/

pip install -e ".[test]" # installation with required packages for testingRecommended way to run the tests:toxTesting the application in the current environment python:maketestWorking with Exchange RatesTo work with exchange rates, add the following to yourINSTALLED_APPS.INSTALLED_APPS=[...,'djmoney.contrib.exchange',]Also, it is required to havecertifiinstalled.
It could be done via installingdjmoneywithexchangeextra:pipinstall"django-money[exchange]"To create required relations runpython manage.py migrate. To fill these relations with data you need to choose a
data source. Currently, 2 data sources are supported -https://openexchangerates.org/(default) andhttps://fixer.io/.
To choose another data source setEXCHANGE_BACKENDsettings with importable string to the backend you need:EXCHANGE_BACKEND='djmoney.contrib.exchange.backends.FixerBackend'If you want to implement your own backend, you need to extenddjmoney.contrib.exchange.backends.base.BaseExchangeBackend.
Two data sources mentioned above are not open, so you have to specify access keys in order to use them:OPEN_EXCHANGE_RATES_APP_ID- ‘<your actual key from openexchangerates.org>’FIXER_ACCESS_KEY- ‘<your actual key from fixer.io>’Backends return rates for a base currency, by default it is USD, but could be changed viaBASE_CURRENCYsetting.
Open Exchanger Rates & Fixer supports some extra stuff, like historical data or restricting currencies
in responses to the certain list. In order to use these features you could change default URLs for these backends: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'Or, you could pass it directly toupdate_ratesmethod:>>>fromdjmoney.contrib.exchange.backendsimportOpenExchangeRatesBackend>>>backend=OpenExchangeRatesBackend(url='https://openexchangerates.org/api/historical/2017-01-01.json')>>>backend.update_rates(symbols='EUR,NOK,SEK,CZK')There is a possibility to use multiple backends in the same time:>>>fromdjmoney.contrib.exchange.backendsimportFixerBackend,OpenExchangeRatesBackend>>>fromdjmoney.contrib.exchange.modelsimportget_rate>>>OpenExchangeRatesBackend().update_rates()>>>FixerBackend().update_rates()>>>get_rate('USD','EUR',backend=OpenExchangeRatesBackend.name)>>>get_rate('USD','EUR',backend=FixerBackend.name)Regular operations withMoneywill useEXCHANGE_BACKENDbackend to get the rates.
Also, there are two management commands for updating rates and removing them:$pythonmanage.pyupdate_ratesSuccessfullyupdatedratesfromopenexchangerates.org$pythonmanage.pyclear_ratesSuccessfullyclearedratesforopenexchangerates.orgBoth of them accept-b/--backendoption, that will update/clear data only for this backend.
Andclear_ratesaccepts-a/--alloption, that will clear data for all backends.To set up a periodic rates update you could use Celery task:CELERYBEAT_SCHEDULE={'update_rates':{'task':'path.to.your.task','schedule':crontab(minute=0,hour=0),'kwargs':{}# For custom arguments}}Example task implementation:fromdjango.utils.module_loadingimportimport_stringfromceleryimportCeleryfromdjmoneyimportsettingsapp=Celery('tasks',broker='pyamqp://guest@localhost//')@app.taskdefupdate_rates(backend=settings.EXCHANGE_BACKEND,**kwargs):backend=import_string(backend)()backend.update_rates(**kwargs)To convert one currency to another:>>>fromdjmoney.moneyimportMoney>>>fromdjmoney.contrib.exchange.modelsimportconvert_money>>>convert_money(Money(100,'EUR'),'USD')<Money:122.8184375038380800USD>Exchange rates are integrated with Django Admin.django-money can be configured to automatically use this app for currency
conversions by settingsAUTO_CONVERT_MONEY = Truein your Django
settings. Note that currency conversion is a lossy process, so automatic
conversion is usually a good strategy only for very simple use cases. For most
use cases you will need to be clear about exactly when currency conversion
occurs, and automatic conversion can hide bugs. Also, with automatic conversion
you lose some properties like commutativity (A + B == B + A) due to
conversions happening in different directions.Usage with Django REST FrameworkMake sure thatdjmoneyand is in theINSTALLED_APPSof yoursettings.pyand thatrest_frameworkhas been installed. MoneyField will
automatically register a serializer for Django REST Framework throughdjmoney.apps.MoneyConfig.ready().You can add a serializable field the following way:fromdjmoney.contrib.django_rest_frameworkimportMoneyFieldclassSerializers(serializers.Serializer):my_computed_prop=MoneyField(max_digits=10,decimal_places=2)Built-in serializer works in the following way:classExpenses(models.Model):amount=MoneyField(max_digits=10,decimal_places=2)classSerializer(serializers.ModelSerializer):classMeta:model=Expensesfields='__all__'>>>instance=Expenses.objects.create(amount=Money(10,'EUR'))>>>serializer=Serializer(instance=instance)>>>serializer.dataReturnDict([('id',1),('amount_currency','EUR'),('amount','10.000'),])Note that when specifying individual fields on your serializer, the amount and currency fields are treated separately.
To achieve the same behaviour as above you would include both field names:classSerializer(serializers.ModelSerializer):classMeta:model=Expensesfields=('id','amount','amount_currency')CustomizationIf there is a need to customize the process deconstructingMoneyinstances onto Django Fields and the other way around,
then it is possible to use a custom descriptor like this one:classMyMoneyDescriptor:def__get__(self,obj,type=None):amount=obj.__dict__[self.field.name]returnMoney(amount,"EUR")It will always useEURfor allMoneyinstances whenobj.moneyis called. Then it should be passed toMoneyField:classExpenses(models.Model):amount=MoneyField(max_digits=10,decimal_places=2,money_descriptor_class=MyMoneyDescriptor)BackgroundThis project is a fork of the Django support that was inhttp://code.google.com/p/python-money/This version adds tests, and comes with several critical bugfixes.