├── .coveragerc ├── .github └── workflows │ └── tox.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── django_prices_openexchangerates ├── __init__.py ├── admin.py ├── apps.py ├── currencies.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── update_exchange_rates.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20160329_0702.py │ ├── 0003_auto_20161018_0707.py │ ├── 0004_auto_20170316_0944.py │ ├── 0005_auto_20190124_1008.py │ └── __init__.py ├── models.py ├── tasks.py └── templatetags │ ├── __init__.py │ └── prices_multicurrency.py ├── setup.cfg ├── setup.py ├── test_settings.py ├── tests ├── __init__.py ├── settings.py └── test_openexchangerates.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = 1 3 | omit = 4 | */migrations/* 5 | */tests.py 6 | source = django_prices_openexchangerates 7 | 8 | [report] 9 | exclude_lines = 10 | noqa 11 | pragma: no cover 12 | raise NotImplementedError 13 | return NotImplemented 14 | -------------------------------------------------------------------------------- /.github/workflows/tox.yml: -------------------------------------------------------------------------------- 1 | name: Tox 2 | on: 3 | push: 4 | branches: [ master ] 5 | pull_request: 6 | branches: [ master ] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | python-version: ['3.7', '3.8', '3.9', '3.10'] 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Set up Python ${{ matrix.python-version }} 19 | uses: actions/setup-python@v3 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | - name: Install dependencies 23 | run: | 24 | python -m pip install --upgrade pip 25 | python -m pip install tox tox-gh-actions 26 | - name: Test with tox 27 | run: tox 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | .mypy_cache 41 | .pytest_cache 42 | nosetests.xml 43 | coverage.xml 44 | 45 | # Translations 46 | *.mo 47 | *.pot 48 | 49 | # Django stuff: 50 | *.log 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | # PyBuilder 56 | target/ 57 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: false 3 | python: 4 | - "3.6" 5 | - "3.7" 6 | - "3.8" 7 | cache: 8 | pip: true 9 | install: 10 | - pip install mypy tox-travis codecov 11 | script: 12 | - tox 13 | - mypy django_prices_openexchangerates --ignore-missing-imports 14 | env: 15 | matrix: 16 | - DJANGO="2.2" 17 | - DJANGO="3.0" 18 | - DJANGO="master" 19 | matrix: 20 | allow_failures: 21 | - python: "3.6" 22 | env: DJANGO="master" 23 | - python: "3.7" 24 | env: DJANGO="master" 25 | - python: "3.8" 26 | env: DJANGO="master" 27 | after_success: 28 | - codecov 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Mirumee Labs 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of django-prices-openexchangerates nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # openexchangerates.org support for `django-prices` 2 | 3 | ```python 4 | from prices import Money 5 | from django_prices_openexchangerates import exchange_currency 6 | 7 | converted_price = exchange_currency(Money(10, currency='USD'), 'EUR') 8 | print(converted_price) 9 | # Money('8.84040', currency='EUR') 10 | ``` 11 | 12 | It will also create additional steps if it cannot convert directly: 13 | 14 | ```python 15 | from prices import Money 16 | from django_prices_openexchangerates import exchange_currency 17 | 18 | converted_price = exchange_currency(Money(10, currency='GBP'), 'EUR') 19 | print(converted_price) 20 | # Money('13.31313588062401085236264978', currency='EUR') 21 | ``` 22 | 23 | The `exchange_currency` supports `Money`, `TaxedMoney`, `MoneyRange` and `TaxedMoneyRange`. 24 | 25 | Template filters can be used with `django-prices` to convert currency, round amounts and display localized amounts in templates: 26 | 27 | ```html+django 28 | {% load prices_i18n %} 29 | {% load prices_multicurrency %} 30 | 31 |

Price: {{ foo.price.gross|in_currency:'USD'|amount }} ({{ foo.price.net|in_currency:'USD'|amount }} + {{ foo.price.tax|in_currency:'USD'|amount }} tax)

32 | ``` 33 | 34 | 35 | Installation 36 | ============== 37 | First install the package: 38 | ``` 39 | pip install django-prices-openexchangerates 40 | ``` 41 | Then add `'django_prices_openexchangerates'` to your `INSTALLED_APPS`. 42 | 43 | Set following settings in your project's settings: 44 | 45 | * `OPENEXCHANGERATES_API_KEY` 46 | 47 | * `OPENEXCHANGERATES_BASE_CURRENCY` (defaults to `'USD'`, only premium accounts support other bases) 48 | 49 | Use your admin console to create `ConversionRate` objects for each currency that you want to support. 50 | 51 | Updating exchange rates 52 | ======================= 53 | Fetch current rates from API with `./manage.py update_exchange_rates` 54 | 55 | Schedule this task in cron job or in celery, to be always up to date with exchange rates 56 | 57 | You can use `--all` flag in above command, to create exchange rates automatically for all available currencies. 58 | -------------------------------------------------------------------------------- /django_prices_openexchangerates/__init__.py: -------------------------------------------------------------------------------- 1 | import operator 2 | from decimal import Decimal 3 | from typing import Callable, TypeVar 4 | 5 | from django.conf import settings 6 | from prices import Money, MoneyRange, TaxedMoney, TaxedMoneyRange 7 | 8 | T = TypeVar('T', Money, MoneyRange, TaxedMoney, TaxedMoneyRange) 9 | 10 | BASE_CURRENCY = getattr(settings, 'OPENEXCHANGERATES_BASE_CURRENCY', 'USD') 11 | 12 | default_app_config = 'django_prices_openexchangerates.apps.DjangoPricesOpenExchangeRatesConfig' 13 | 14 | 15 | def get_rate_from_db(currency: str) -> Decimal: 16 | """ 17 | Fetch currency conversion rate from the database 18 | """ 19 | from .models import ConversionRate 20 | try: 21 | rate = ConversionRate.objects.get_rate(currency) 22 | except ConversionRate.DoesNotExist: # noqa 23 | raise ValueError('No conversion rate for %s' % (currency, )) 24 | return rate.rate 25 | 26 | 27 | def get_conversion_rate(from_currency: str, to_currency: str) -> Decimal: 28 | """ 29 | Get conversion rate to use in exchange 30 | """ 31 | reverse_rate = False 32 | if to_currency == BASE_CURRENCY: 33 | # Fetch exchange rate for base currency and use 1 / rate for conversion 34 | rate_currency = from_currency 35 | reverse_rate = True 36 | else: 37 | rate_currency = to_currency 38 | rate = get_rate_from_db(rate_currency) 39 | 40 | if reverse_rate: 41 | conversion_rate = Decimal(1) / rate 42 | else: 43 | conversion_rate = rate 44 | return conversion_rate 45 | 46 | 47 | def exchange_currency( 48 | base: T, to_currency: str, *, conversion_rate: Decimal=None) -> T: 49 | """ 50 | Exchanges Money, TaxedMoney and their ranges to the specified currency. 51 | get_rate parameter is a callable taking single argument (target currency) 52 | that returns proper conversion rate 53 | """ 54 | if base.currency == to_currency: 55 | return base 56 | if base.currency != BASE_CURRENCY and to_currency != BASE_CURRENCY: 57 | # Exchange to base currency first 58 | base = exchange_currency(base, BASE_CURRENCY) 59 | 60 | if conversion_rate is None: 61 | conversion_rate = get_conversion_rate(base.currency, to_currency) 62 | 63 | if isinstance(base, Money): 64 | return Money(base.amount * conversion_rate, currency=to_currency) 65 | if isinstance(base, MoneyRange): 66 | return MoneyRange( 67 | exchange_currency( 68 | base.start, to_currency, conversion_rate=conversion_rate), 69 | exchange_currency( 70 | base.stop, to_currency, conversion_rate=conversion_rate)) 71 | if isinstance(base, TaxedMoney): 72 | return TaxedMoney( 73 | exchange_currency( 74 | base.net, to_currency, conversion_rate=conversion_rate), 75 | exchange_currency( 76 | base.gross, to_currency, conversion_rate=conversion_rate)) 77 | if isinstance(base, TaxedMoneyRange): 78 | return TaxedMoneyRange( 79 | exchange_currency( 80 | base.start, to_currency, conversion_rate=conversion_rate), 81 | exchange_currency( 82 | base.stop, to_currency, conversion_rate=conversion_rate)) 83 | 84 | # base.currency was set but we don't know how to exchange given type 85 | raise TypeError('Unknown base for exchange_currency: %r' % (base,)) 86 | -------------------------------------------------------------------------------- /django_prices_openexchangerates/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import ConversionRate 3 | 4 | 5 | class ConversionRateAdmin(admin.ModelAdmin): 6 | list_display = ('rate', 'to_currency') 7 | 8 | admin.site.register(ConversionRate, ConversionRateAdmin) 9 | -------------------------------------------------------------------------------- /django_prices_openexchangerates/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class DjangoPricesOpenExchangeRatesConfig(AppConfig): 5 | name = 'django_prices_openexchangerates' 6 | verbose_name = "Django prices openexchangerates integration" 7 | -------------------------------------------------------------------------------- /django_prices_openexchangerates/currencies.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from django.utils.translation import gettext_lazy as _ 3 | 4 | # List of currencies supported by OpenExchange 5 | # http://openexchangerates.org/currencies.json 6 | 7 | 8 | CURRENCIES = [ 9 | ("AED", _("United Arab Emirates Dirham")), 10 | ("AFN", _("Afghan Afghani")), 11 | ("ALL", _("Albanian Lek")), 12 | ("AMD", _("Armenian Dram")), 13 | ("ANG", _("Netherlands Antillean Guilder")), 14 | ("AOA", _("Angolan Kwanza")), 15 | ("ARS", _("Argentine Peso")), 16 | ("AUD", _("Australian Dollar")), 17 | ("AWG", _("Aruban Florin")), 18 | ("AZN", _("Azerbaijani Manat")), 19 | ("BAM", _("Bosnia-Herzegovina Convertible Mark")), 20 | ("BBD", _("Barbadian Dollar")), 21 | ("BDT", _("Bangladeshi Taka")), 22 | ("BGN", _("Bulgarian Lev")), 23 | ("BHD", _("Bahraini Dinar")), 24 | ("BIF", _("Burundian Franc")), 25 | ("BMD", _("Bermudan Dollar")), 26 | ("BND", _("Brunei Dollar")), 27 | ("BOB", _("Bolivian Boliviano")), 28 | ("BRL", _("Brazilian Real")), 29 | ("BSD", _("Bahamian Dollar")), 30 | ("BTC", _("Bitcoin")), 31 | ("BTN", _("Bhutanese Ngultrum")), 32 | ("BWP", _("Botswanan Pula")), 33 | ("BYN", _("Belarusian Ruble")), 34 | ("BZD", _("Belize Dollar")), 35 | ("CAD", _("Canadian Dollar")), 36 | ("CDF", _("Congolese Franc")), 37 | ("CHF", _("Swiss Franc")), 38 | ("CLF", _("Chilean Unit of Account (UF)")), 39 | ("CLP", _("Chilean Peso")), 40 | ("CNH", _("Chinese Yuan (Offshore)")), 41 | ("CNY", _("Chinese Yuan")), 42 | ("COP", _("Colombian Peso")), 43 | ("CRC", _("Costa Rican Colón")), 44 | ("CUC", _("Cuban Convertible Peso")), 45 | ("CUP", _("Cuban Peso")), 46 | ("CVE", _("Cape Verdean Escudo")), 47 | ("CZK", _("Czech Republic Koruna")), 48 | ("DJF", _("Djiboutian Franc")), 49 | ("DKK", _("Danish Krone")), 50 | ("DOP", _("Dominican Peso")), 51 | ("DZD", _("Algerian Dinar")), 52 | ("EGP", _("Egyptian Pound")), 53 | ("ERN", _("Eritrean Nakfa")), 54 | ("ETB", _("Ethiopian Birr")), 55 | ("EUR", _("Euro")), 56 | ("FJD", _("Fijian Dollar")), 57 | ("FKP", _("Falkland Islands Pound")), 58 | ("GBP", _("British Pound Sterling")), 59 | ("GEL", _("Georgian Lari")), 60 | ("GGP", _("Guernsey Pound")), 61 | ("GHS", _("Ghanaian Cedi")), 62 | ("GIP", _("Gibraltar Pound")), 63 | ("GMD", _("Gambian Dalasi")), 64 | ("GNF", _("Guinean Franc")), 65 | ("GTQ", _("Guatemalan Quetzal")), 66 | ("GYD", _("Guyanaese Dollar")), 67 | ("HKD", _("Hong Kong Dollar")), 68 | ("HNL", _("Honduran Lempira")), 69 | ("HRK", _("Croatian Kuna")), 70 | ("HTG", _("Haitian Gourde")), 71 | ("HUF", _("Hungarian Forint")), 72 | ("IDR", _("Indonesian Rupiah")), 73 | ("ILS", _("Israeli New Sheqel")), 74 | ("IMP", _("Manx pound")), 75 | ("INR", _("Indian Rupee")), 76 | ("IQD", _("Iraqi Dinar")), 77 | ("IRR", _("Iranian Rial")), 78 | ("ISK", _("Icelandic Króna")), 79 | ("JEP", _("Jersey Pound")), 80 | ("JMD", _("Jamaican Dollar")), 81 | ("JOD", _("Jordanian Dinar")), 82 | ("JPY", _("Japanese Yen")), 83 | ("KES", _("Kenyan Shilling")), 84 | ("KGS", _("Kyrgystani Som")), 85 | ("KHR", _("Cambodian Riel")), 86 | ("KMF", _("Comorian Franc")), 87 | ("KPW", _("North Korean Won")), 88 | ("KRW", _("South Korean Won")), 89 | ("KWD", _("Kuwaiti Dinar")), 90 | ("KYD", _("Cayman Islands Dollar")), 91 | ("KZT", _("Kazakhstani Tenge")), 92 | ("LAK", _("Laotian Kip")), 93 | ("LBP", _("Lebanese Pound")), 94 | ("LKR", _("Sri Lankan Rupee")), 95 | ("LRD", _("Liberian Dollar")), 96 | ("LSL", _("Lesotho Loti")), 97 | ("LYD", _("Libyan Dinar")), 98 | ("MAD", _("Moroccan Dirham")), 99 | ("MDL", _("Moldovan Leu")), 100 | ("MGA", _("Malagasy Ariary")), 101 | ("MKD", _("Macedonian Denar")), 102 | ("MMK", _("Myanma Kyat")), 103 | ("MNT", _("Mongolian Tugrik")), 104 | ("MOP", _("Macanese Pataca")), 105 | ("MRO", _("Mauritanian Ouguiya (pre-2018)")), 106 | ("MRU", _("Mauritanian Ouguiya")), 107 | ("MUR", _("Mauritian Rupee")), 108 | ("MVR", _("Maldivian Rufiyaa")), 109 | ("MWK", _("Malawian Kwacha")), 110 | ("MXN", _("Mexican Peso")), 111 | ("MYR", _("Malaysian Ringgit")), 112 | ("MZN", _("Mozambican Metical")), 113 | ("NAD", _("Namibian Dollar")), 114 | ("NGN", _("Nigerian Naira")), 115 | ("NIO", _("Nicaraguan Córdoba")), 116 | ("NOK", _("Norwegian Krone")), 117 | ("NPR", _("Nepalese Rupee")), 118 | ("NZD", _("New Zealand Dollar")), 119 | ("OMR", _("Omani Rial")), 120 | ("PAB", _("Panamanian Balboa")), 121 | ("PEN", _("Peruvian Nuevo Sol")), 122 | ("PGK", _("Papua New Guinean Kina")), 123 | ("PHP", _("Philippine Peso")), 124 | ("PKR", _("Pakistani Rupee")), 125 | ("PLN", _("Polish Zloty")), 126 | ("PYG", _("Paraguayan Guarani")), 127 | ("QAR", _("Qatari Rial")), 128 | ("RON", _("Romanian Leu")), 129 | ("RSD", _("Serbian Dinar")), 130 | ("RUB", _("Russian Ruble")), 131 | ("RWF", _("Rwandan Franc")), 132 | ("SAR", _("Saudi Riyal")), 133 | ("SBD", _("Solomon Islands Dollar")), 134 | ("SCR", _("Seychellois Rupee")), 135 | ("SDG", _("Sudanese Pound")), 136 | ("SEK", _("Swedish Krona")), 137 | ("SGD", _("Singapore Dollar")), 138 | ("SHP", _("Saint Helena Pound")), 139 | ("SLL", _("Sierra Leonean Leone")), 140 | ("SOS", _("Somali Shilling")), 141 | ("SRD", _("Surinamese Dollar")), 142 | ("SSP", _("South Sudanese Pound")), 143 | ("STD", _("São Tomé and Príncipe Dobra (pre-2018)")), 144 | ("STN", _("São Tomé and Príncipe Dobra")), 145 | ("SVC", _("Salvadoran Colón")), 146 | ("SYP", _("Syrian Pound")), 147 | ("SZL", _("Swazi Lilangeni")), 148 | ("THB", _("Thai Baht")), 149 | ("TJS", _("Tajikistani Somoni")), 150 | ("TMT", _("Turkmenistani Manat")), 151 | ("TND", _("Tunisian Dinar")), 152 | ("TOP", _("Tongan Pa'anga")), 153 | ("TRY", _("Turkish Lira")), 154 | ("TTD", _("Trinidad and Tobago Dollar")), 155 | ("TWD", _("New Taiwan Dollar")), 156 | ("TZS", _("Tanzanian Shilling")), 157 | ("UAH", _("Ukrainian Hryvnia")), 158 | ("UGX", _("Ugandan Shilling")), 159 | ("USD", _("United States Dollar")), 160 | ("UYU", _("Uruguayan Peso")), 161 | ("UZS", _("Uzbekistan Som")), 162 | ("VEF", _("Venezuelan Bolívar Fuerte (Old)")), 163 | ("VES", _("Venezuelan Bolívar Soberano")), 164 | ("VND", _("Vietnamese Dong")), 165 | ("VUV", _("Vanuatu Vatu")), 166 | ("WST", _("Samoan Tala")), 167 | ("XAF", _("CFA Franc BEAC")), 168 | ("XAG", _("Silver Ounce")), 169 | ("XAU", _("Gold Ounce")), 170 | ("XCD", _("East Caribbean Dollar")), 171 | ("XDR", _("Special Drawing Rights")), 172 | ("XOF", _("CFA Franc BCEAO")), 173 | ("XPD", _("Palladium Ounce")), 174 | ("XPF", _("CFP Franc")), 175 | ("XPT", _("Platinum Ounce")), 176 | ("YER", _("Yemeni Rial")), 177 | ("ZAR", _("South African Rand")), 178 | ("ZMW", _("Zambian Kwacha")), 179 | ("ZWL", _("Zimbabwean Dollar")), 180 | ] 181 | -------------------------------------------------------------------------------- /django_prices_openexchangerates/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirumee/django-prices-openexchangerates/1d91c4b376894a72992b37893b582aee84f6a0f9/django_prices_openexchangerates/management/__init__.py -------------------------------------------------------------------------------- /django_prices_openexchangerates/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirumee/django-prices-openexchangerates/1d91c4b376894a72992b37893b582aee84f6a0f9/django_prices_openexchangerates/management/commands/__init__.py -------------------------------------------------------------------------------- /django_prices_openexchangerates/management/commands/update_exchange_rates.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | 3 | from ...tasks import update_conversion_rates, create_conversion_rates 4 | 5 | 6 | class Command(BaseCommand): 7 | 8 | def add_arguments(self, parser): 9 | parser.add_argument( 10 | '--all', 11 | action='store_true', 12 | dest='all_currencies', 13 | default=False, 14 | help='Create entries for all currencies') 15 | 16 | def handle(self, *args, **options): 17 | if options['all_currencies']: 18 | all_rates = create_conversion_rates() 19 | else: 20 | all_rates = update_conversion_rates() 21 | for conversion_rate in all_rates: 22 | self.stdout.write('%s' % (conversion_rate, )) 23 | -------------------------------------------------------------------------------- /django_prices_openexchangerates/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.2 on 2016-03-17 09:36 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | operations = [ 11 | migrations.CreateModel( 12 | name='ConversionRate', 13 | fields=[ 14 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 15 | ('to_currency', models.CharField(choices=[('COP', 'Colombian Peso'), ('MYR', 'Malaysian Ringgit'), ('TMT', 'Turkmenistani Manat'), ('DZD', 'Algerian Dinar'), ('NAD', 'Namibian Dollar'), ('GHS', 'Ghanaian Cedi'), ('SZL', 'Swazi Lilangeni'), ('EGP', 'Egyptian Pound'), ('IDR', 'Indonesian Rupiah'), ('HNL', 'Honduran Lempira'), ('BGN', 'Bulgarian Lev'), ('FJD', 'Fijian Dollar'), ('ETB', 'Ethiopian Birr'), ('XCD', 'East Caribbean Dollar'), ('PAB', 'Panamanian Balboa'), ('BZD', 'Belize Dollar'), ('USD', 'United States Dollar'), ('ILS', 'Israeli New Sheqel'), ('MGA', 'Malagasy Ariary'), ('BOB', 'Bolivian Boliviano'), ('DKK', 'Danish Krone'), ('BWP', 'Botswanan Pula'), ('LBP', 'Lebanese Pound'), ('TZS', 'Tanzanian Shilling'), ('VND', 'Vietnamese Dong'), ('GGP', 'Guernsey Pound'), ('AOA', 'Angolan Kwanza'), ('WST', 'Samoan Tala'), ('KHR', 'Cambodian Riel'), ('MUR', 'Mauritian Rupee'), ('SOS', 'Somali Shilling'), ('KYD', 'Cayman Islands Dollar'), ('LYD', 'Libyan Dinar'), ('UAH', 'Ukrainian Hryvnia'), ('UGX', 'Ugandan Shilling'), ('JOD', 'Jordanian Dinar'), ('ZMW', 'Zambian Kwacha'), ('AWG', 'Aruban Florin'), ('SAR', 'Saudi Riyal'), ('EUR', 'Euro'), ('SEK', 'Swedish Krona'), ('TOP', 'Tongan Pa\u02bbanga'), ('HKD', 'Hong Kong Dollar'), ('JEP', 'Jersey Pound'), ('MDL', 'Moldovan Le'), ('AUD', 'Australian Dollar'), ('CHF', 'Swiss Franc'), ('CUP', 'Cuban Peso'), ('CLF', 'Chilean Unit of Account (UF)'), ('BBD', 'Barbadian Dollar'), ('BYR', 'Belarusian Ruble'), ('CDF', 'Congolese Franc'), ('GMD', 'Gambian Dalasi'), ('VEF', 'Venezuelan Bol\xedvar Fuerte'), ('BSD', 'Bahamian Dollar'), ('ARS', 'Argentine Peso'), ('TND', 'Tunisian Dinar'), ('HRK', 'Croatian Kuna'), ('DJF', 'Djiboutian Franc'), ('YER', 'Yemeni Rial'), ('SYP', 'Syrian Pound'), ('CLP', 'Chilean Peso'), ('THB', 'Thai Baht'), ('XAF', 'CFA Franc BEAC'), ('BND', 'Brunei Dollar'), ('ISK', 'Icelandic Kr\xf3na'), ('ALL', 'Albanian Lek'), ('SRD', 'Surinamese Dollar'), ('NIO', 'Nicaraguan C\xf3rdoba'), ('KZT', 'Kazakhstani Tenge'), ('LAK', 'Laotian Kip'), ('RUB', 'Russian Ruble'), ('XAG', 'Silver (troy ounce)'), ('NOK', 'Norwegian Krone'), ('PYG', 'Paraguayan Guarani'), ('PEN', 'Peruvian Nuevo Sol'), ('RON', 'Romanian Le'), ('OMR', 'Omani Rial'), ('BRL', 'Brazilian Real'), ('MAD', 'Moroccan Dirham'), ('MMK', 'Myanma Kyat'), ('PLN', 'Polish Zloty'), ('MZN', 'Mozambican Metical'), ('PHP', 'Philippine Peso'), ('KES', 'Kenyan Shilling'), ('SVC', 'Salvadoran Col\xf3n'), ('NPR', 'Nepalese Rupee'), ('STD', 'S\xe3o Tom\xe9 and Pr\xedncipe Dobra'), ('MKD', 'Macedonian Denar'), ('ZWL', 'Zimbabwean Dollar'), ('GBP', 'British Pound Sterling'), ('AZN', 'Azerbaijani Manat'), ('NGN', 'Nigerian Naira'), ('MVR', 'Maldivian Rufiyaa'), ('VUV', 'Vanuatu Vat'), ('CRC', 'Costa Rican Col\xf3n'), ('GNF', 'Guinean Franc'), ('AED', 'United Arab Emirates Dirham'), ('EEK', 'Estonian Kroon'), ('MWK', 'Malawian Kwacha'), ('IQD', 'Iraqi Dinar'), ('ERN', 'Eritrean Nakfa'), ('BAM', 'Bosnia-Herzegovina Convertible Mark'), ('LKR', 'Sri Lankan Rupee'), ('DOP', 'Dominican Peso'), ('TTD', 'Trinidad and Tobago Dollar'), ('CAD', 'Canadian Dollar'), ('PKR', 'Pakistani Rupee'), ('MXN', 'Mexican Peso'), ('HUF', 'Hungarian Forint'), ('CVE', 'Cape Verdean Escudo'), ('KWD', 'Kuwaiti Dinar'), ('BMD', 'Bermudan Dollar'), ('BIF', 'Burundian Franc'), ('LSL', 'Lesotho Loti'), ('GIP', 'Gibraltar Pound'), ('MNT', 'Mongolian Tugrik'), ('AMD', 'Armenian Dram'), ('UZS', 'Uzbekistan Som'), ('LTL', 'Lithuanian Litas'), ('SDG', 'Sudanese Pound'), ('QAR', 'Qatari Rial'), ('XDR', 'Special Drawing Rights'), ('KRW', 'South Korean Won'), ('TWD', 'New Taiwan Dollar'), ('SGD', 'Singapore Dollar'), ('JMD', 'Jamaican Dollar'), ('GEL', 'Georgian Lari'), ('SHP', 'Saint Helena Pound'), ('AFN', 'Afghan Afghani'), ('XA', 'Gold (troy ounce)'), ('SBD', 'Solomon Islands Dollar'), ('KPW', 'North Korean Won'), ('IRR', 'Iranian Rial'), ('SCR', 'Seychellois Rupee'), ('CNY', 'Chinese Yuan'), ('KMF', 'Comorian Franc'), ('BDT', 'Bangladeshi Taka'), ('XOF', 'CFA Franc BCEAO'), ('GYD', 'Guyanaese Dollar'), ('MTL', 'Maltese Lira'), ('NZD', 'New Zealand Dollar'), ('FKP', 'Falkland Islands Pound'), ('LVL', 'Latvian Lats'), ('TRY', 'Turkish Lira'), ('XPF', 'CFP Franc'), ('IMP', 'Manx pound'), ('HTG', 'Haitian Gourde'), ('SLL', 'Sierra Leonean Leone'), ('KGS', 'Kyrgystani Som'), ('ANG', 'Netherlands Antillean Guilder'), ('UY', 'Uruguayan Peso'), ('LRD', 'Liberian Dollar'), ('RWF', 'Rwandan Franc'), ('GTQ', 'Guatemalan Quetzal'), ('RSD', 'Serbian Dinar'), ('ZAR', 'South African Rand'), ('MOP', 'Macanese Pataca'), ('BHD', 'Bahraini Dinar'), ('INR', 'Indian Rupee'), ('JPY', 'Japanese Yen'), ('CZK', 'Czech Republic Koruna'), ('TJS', 'Tajikistani Somoni'), ('MRO', 'Mauritanian Ouguiya'), ('PGK', 'Papua New Guinean Kina'), ('BTC', 'Bitcoin'), ('BTN', 'Bhutanese Ngultrum')], db_index=True, max_length=3, unique=True, verbose_name='To')), 16 | ('rate', models.DecimalField(decimal_places=5, max_digits=12, verbose_name='Conversion rate')), 17 | ('modified_at', models.DateTimeField(auto_now=True)), 18 | ], 19 | options={ 20 | 'ordering': ['to_currency'], 21 | }, 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /django_prices_openexchangerates/migrations/0002_auto_20160329_0702.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.2 on 2016-03-29 12:02 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('django_prices_openexchangerates', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='conversionrate', 15 | name='rate', 16 | field=models.DecimalField(decimal_places=6, max_digits=13, verbose_name='Conversion rate'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /django_prices_openexchangerates/migrations/0003_auto_20161018_0707.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.1 on 2016-10-18 12:07 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('django_prices_openexchangerates', '0002_auto_20160329_0702'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='conversionrate', 15 | name='rate', 16 | field=models.DecimalField(decimal_places=12, max_digits=20, verbose_name='Conversion rate'), 17 | ), 18 | migrations.AlterField( 19 | model_name='conversionrate', 20 | name='to_currency', 21 | field=models.CharField(choices=[('DZD', 'Algerian Dinar'), ('QAR', 'Qatari Rial'), ('BGN', 'Bulgarian Lev'), ('BMD', 'Bermudan Dollar'), ('PAB', 'Panamanian Balboa'), ('BWP', 'Botswanan Pula'), ('TZS', 'Tanzanian Shilling'), ('VND', 'Vietnamese Dong'), ('KYD', 'Cayman Islands Dollar'), ('UAH', 'Ukrainian Hryvnia'), ('AWG', 'Aruban Florin'), ('GIP', 'Gibraltar Pound'), ('BYR', 'Belarusian Ruble (pre-2016)'), ('ALL', 'Albanian Lek'), ('XPD', 'Palladium Ounce'), ('BYN', 'Belarusian Ruble'), ('DJF', 'Djiboutian Franc'), ('THB', 'Thai Baht'), ('BND', 'Brunei Dollar'), ('NIO', 'Nicaraguan C\xf3rdoba'), ('LAK', 'Laotian Kip'), ('SYP', 'Syrian Pound'), ('MAD', 'Moroccan Dirham'), ('MZN', 'Mozambican Metical'), ('YER', 'Yemeni Rial'), ('ZAR', 'South African Rand'), ('NPR', 'Nepalese Rupee'), ('CRC', 'Costa Rican Col\xf3n'), ('AED', 'United Arab Emirates Dirham'), ('GBP', 'British Pound Sterling'), ('HUF', 'Hungarian Forint'), ('LSL', 'Lesotho Loti'), ('XDR', 'Special Drawing Rights'), ('TTD', 'Trinidad and Tobago Dollar'), ('SBD', 'Solomon Islands Dollar'), ('KPW', 'North Korean Won'), ('ANG', 'Netherlands Antillean Guilder'), ('RWF', 'Rwandan Franc'), ('NOK', 'Norwegian Krone'), ('MOP', 'Macanese Pataca'), ('INR', 'Indian Rupee'), ('MXN', 'Mexican Peso'), ('TJS', 'Tajikistani Somoni'), ('COP', 'Colombian Peso'), ('TMT', 'Turkmenistani Manat'), ('HNL', 'Honduran Lempira'), ('FJD', 'Fijian Dollar'), ('ETB', 'Ethiopian Birr'), ('PEN', 'Peruvian Nuevo Sol'), ('BZD', 'Belize Dollar'), ('ILS', 'Israeli New Sheqel'), ('DOP', 'Dominican Peso'), ('GGP', 'Guernsey Pound'), ('MDL', 'Moldovan Leu'), ('BSD', 'Bahamian Dollar'), ('ZMK', 'Zambian Kwacha (pre-2013)'), ('JEP', 'Jersey Pound'), ('AUD', 'Australian Dollar'), ('SRD', 'Surinamese Dollar'), ('KRW', 'South Korean Won'), ('VEF', 'Venezuelan Bol\xedvar Fuerte'), ('ZMW', 'Zambian Kwacha'), ('LTL', 'Lithuanian Litas'), ('CDF', 'Congolese Franc'), ('RUB', 'Russian Ruble'), ('MMK', 'Myanma Kyat'), ('PLN', 'Polish Zloty'), ('MKD', 'Macedonian Denar'), ('TOP', 'Tongan Pa?anga'), ('GNF', 'Guinean Franc'), ('WST', 'Samoan Tala'), ('ERN', 'Eritrean Nakfa'), ('BAM', 'Bosnia-Herzegovina Convertible Mark'), ('CAD', 'Canadian Dollar'), ('CVE', 'Cape Verdean Escudo'), ('PGK', 'Papua New Guinean Kina'), ('SOS', 'Somali Shilling'), ('STD', 'S\xe3o Tom\xe9 and Pr\xedncipe Dobra'), ('BTC', 'Bitcoin'), ('IRR', 'Iranian Rial'), ('XPF', 'CFP Franc'), ('XOF', 'CFA Franc BCEAO'), ('MTL', 'Maltese Lira'), ('NZD', 'New Zealand Dollar'), ('LVL', 'Latvian Lats'), ('ARS', 'Argentine Peso'), ('RSD', 'Serbian Dinar'), ('BHD', 'Bahraini Dinar'), ('SDG', 'Sudanese Pound'), ('XAU', 'Gold Ounce'), ('NAD', 'Namibian Dollar'), ('GHS', 'Ghanaian Cedi'), ('EGP', 'Egyptian Pound'), ('BOB', 'Bolivian Boliviano'), ('DKK', 'Danish Krone'), ('LBP', 'Lebanese Pound'), ('AOA', 'Angolan Kwanza'), ('KHR', 'Cambodian Riel'), ('MYR', 'Malaysian Ringgit'), ('LYD', 'Libyan Dinar'), ('JOD', 'Jordanian Dinar'), ('SAR', 'Saudi Riyal'), ('XPT', 'Platinum Ounce'), ('HKD', 'Hong Kong Dollar'), ('CHF', 'Swiss Franc'), ('SVC', 'Salvadoran Col\xf3n'), ('MRO', 'Mauritanian Ouguiya'), ('HRK', 'Croatian Kuna'), ('XAF', 'CFA Franc BEAC'), ('XAG', 'Silver Ounce'), ('VUV', 'Vanuatu Vatu'), ('UYU', 'Uruguayan Peso'), ('PYG', 'Paraguayan Guarani'), ('ZWL', 'Zimbabwean Dollar'), ('NGN', 'Nigerian Naira'), ('EEK', 'Estonian Kroon'), ('MWK', 'Malawian Kwacha'), ('LKR', 'Sri Lankan Rupee'), ('PKR', 'Pakistani Rupee'), ('SZL', 'Swazi Lilangeni'), ('MNT', 'Mongolian Tugrik'), ('AMD', 'Armenian Dram'), ('UGX', 'Ugandan Shilling'), ('JMD', 'Jamaican Dollar'), ('SCR', 'Seychellois Rupee'), ('SHP', 'Saint Helena Pound'), ('AFN', 'Afghan Afghani'), ('TRY', 'Turkish Lira'), ('BDT', 'Bangladeshi Taka'), ('HTG', 'Haitian Gourde'), ('MGA', 'Malagasy Ariary'), ('PHP', 'Philippine Peso'), ('LRD', 'Liberian Dollar'), ('XCD', 'East Caribbean Dollar'), ('CZK', 'Czech Republic Koruna'), ('TWD', 'New Taiwan Dollar'), ('BTN', 'Bhutanese Ngultrum'), ('MUR', 'Mauritian Rupee'), ('IDR', 'Indonesian Rupiah'), ('ISK', 'Icelandic Kr\xf3na'), ('SEK', 'Swedish Krona'), ('CUP', 'Cuban Peso'), ('CLF', 'Chilean Unit of Account (UF)'), ('BBD', 'Barbadian Dollar'), ('KMF', 'Comorian Franc'), ('GMD', 'Gambian Dalasi'), ('IMP', 'Manx pound'), ('CUC', 'Cuban Convertible Peso'), ('GEL', 'Georgian Lari'), ('CLP', 'Chilean Peso'), ('EUR', 'Euro'), ('KZT', 'Kazakhstani Tenge'), ('OMR', 'Omani Rial'), ('BRL', 'Brazilian Real'), ('KES', 'Kenyan Shilling'), ('USD', 'United States Dollar'), ('AZN', 'Azerbaijani Manat'), ('MVR', 'Maldivian Rufiyaa'), ('IQD', 'Iraqi Dinar'), ('GYD', 'Guyanaese Dollar'), ('KWD', 'Kuwaiti Dinar'), ('BIF', 'Burundian Franc'), ('SGD', 'Singapore Dollar'), ('UZS', 'Uzbekistan Som'), ('CNY', 'Chinese Yuan'), ('SLL', 'Sierra Leonean Leone'), ('TND', 'Tunisian Dinar'), ('FKP', 'Falkland Islands Pound'), ('KGS', 'Kyrgystani Som'), ('RON', 'Romanian Leu'), ('GTQ', 'Guatemalan Quetzal'), ('JPY', 'Japanese Yen')], db_index=True, max_length=3, unique=True, verbose_name='To'), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /django_prices_openexchangerates/migrations/0004_auto_20170316_0944.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.12 on 2017-03-16 09:44 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('django_prices_openexchangerates', '0003_auto_20161018_0707'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='conversionrate', 15 | name='to_currency', 16 | field=models.CharField(choices=[('AED', 'United Arab Emirates Dirham'), ('AFN', 'Afghan Afghani'), ('ALL', 'Albanian Lek'), ('AMD', 'Armenian Dram'), ('ANG', 'Netherlands Antillean Guilder'), ('AOA', 'Angolan Kwanza'), ('ARS', 'Argentine Peso'), ('AUD', 'Australian Dollar'), ('AWG', 'Aruban Florin'), ('AZN', 'Azerbaijani Manat'), ('BAM', 'Bosnia-Herzegovina Convertible Mark'), ('BBD', 'Barbadian Dollar'), ('BDT', 'Bangladeshi Taka'), ('BGN', 'Bulgarian Lev'), ('BHD', 'Bahraini Dinar'), ('BIF', 'Burundian Franc'), ('BMD', 'Bermudan Dollar'), ('BND', 'Brunei Dollar'), ('BOB', 'Bolivian Boliviano'), ('BRL', 'Brazilian Real'), ('BSD', 'Bahamian Dollar'), ('BTC', 'Bitcoin'), ('BTN', 'Bhutanese Ngultrum'), ('BWP', 'Botswanan Pula'), ('BYN', 'Belarusian Ruble'), ('BYR', 'Belarusian Ruble (pre-2016)'), ('BZD', 'Belize Dollar'), ('CAD', 'Canadian Dollar'), ('CDF', 'Congolese Franc'), ('CHF', 'Swiss Franc'), ('CLF', 'Chilean Unit of Account (UF)'), ('CLP', 'Chilean Peso'), ('CNY', 'Chinese Yuan'), ('COP', 'Colombian Peso'), ('CRC', 'Costa Rican Colón'), ('CUC', 'Cuban Convertible Peso'), ('CUP', 'Cuban Peso'), ('CVE', 'Cape Verdean Escudo'), ('CZK', 'Czech Republic Koruna'), ('DJF', 'Djiboutian Franc'), ('DKK', 'Danish Krone'), ('DOP', 'Dominican Peso'), ('DZD', 'Algerian Dinar'), ('EEK', 'Estonian Kroon'), ('EGP', 'Egyptian Pound'), ('ERN', 'Eritrean Nakfa'), ('ETB', 'Ethiopian Birr'), ('EUR', 'Euro'), ('FJD', 'Fijian Dollar'), ('FKP', 'Falkland Islands Pound'), ('GBP', 'British Pound Sterling'), ('GEL', 'Georgian Lari'), ('GGP', 'Guernsey Pound'), ('GHS', 'Ghanaian Cedi'), ('GIP', 'Gibraltar Pound'), ('GMD', 'Gambian Dalasi'), ('GNF', 'Guinean Franc'), ('GTQ', 'Guatemalan Quetzal'), ('GYD', 'Guyanaese Dollar'), ('HKD', 'Hong Kong Dollar'), ('HNL', 'Honduran Lempira'), ('HRK', 'Croatian Kuna'), ('HTG', 'Haitian Gourde'), ('HUF', 'Hungarian Forint'), ('IDR', 'Indonesian Rupiah'), ('ILS', 'Israeli New Sheqel'), ('IMP', 'Manx pound'), ('INR', 'Indian Rupee'), ('IQD', 'Iraqi Dinar'), ('IRR', 'Iranian Rial'), ('ISK', 'Icelandic Króna'), ('JEP', 'Jersey Pound'), ('JMD', 'Jamaican Dollar'), ('JOD', 'Jordanian Dinar'), ('JPY', 'Japanese Yen'), ('KES', 'Kenyan Shilling'), ('KGS', 'Kyrgystani Som'), ('KHR', 'Cambodian Riel'), ('KMF', 'Comorian Franc'), ('KPW', 'North Korean Won'), ('KRW', 'South Korean Won'), ('KWD', 'Kuwaiti Dinar'), ('KYD', 'Cayman Islands Dollar'), ('KZT', 'Kazakhstani Tenge'), ('LAK', 'Laotian Kip'), ('LBP', 'Lebanese Pound'), ('LKR', 'Sri Lankan Rupee'), ('LRD', 'Liberian Dollar'), ('LSL', 'Lesotho Loti'), ('LTL', 'Lithuanian Litas'), ('LVL', 'Latvian Lats'), ('LYD', 'Libyan Dinar'), ('MAD', 'Moroccan Dirham'), ('MDL', 'Moldovan Leu'), ('MGA', 'Malagasy Ariary'), ('MKD', 'Macedonian Denar'), ('MMK', 'Myanma Kyat'), ('MNT', 'Mongolian Tugrik'), ('MOP', 'Macanese Pataca'), ('MRO', 'Mauritanian Ouguiya'), ('MTL', 'Maltese Lira'), ('MUR', 'Mauritian Rupee'), ('MVR', 'Maldivian Rufiyaa'), ('MWK', 'Malawian Kwacha'), ('MXN', 'Mexican Peso'), ('MYR', 'Malaysian Ringgit'), ('MZN', 'Mozambican Metical'), ('NAD', 'Namibian Dollar'), ('NGN', 'Nigerian Naira'), ('NIO', 'Nicaraguan Córdoba'), ('NOK', 'Norwegian Krone'), ('NPR', 'Nepalese Rupee'), ('NZD', 'New Zealand Dollar'), ('OMR', 'Omani Rial'), ('PAB', 'Panamanian Balboa'), ('PEN', 'Peruvian Nuevo Sol'), ('PGK', 'Papua New Guinean Kina'), ('PHP', 'Philippine Peso'), ('PKR', 'Pakistani Rupee'), ('PLN', 'Polish Zloty'), ('PYG', 'Paraguayan Guarani'), ('QAR', 'Qatari Rial'), ('RON', 'Romanian Leu'), ('RSD', 'Serbian Dinar'), ('RUB', 'Russian Ruble'), ('RWF', 'Rwandan Franc'), ('SAR', 'Saudi Riyal'), ('SBD', 'Solomon Islands Dollar'), ('SCR', 'Seychellois Rupee'), ('SDG', 'Sudanese Pound'), ('SEK', 'Swedish Krona'), ('SGD', 'Singapore Dollar'), ('SHP', 'Saint Helena Pound'), ('SLL', 'Sierra Leonean Leone'), ('SOS', 'Somali Shilling'), ('SRD', 'Surinamese Dollar'), ('STD', 'São Tomé and Príncipe Dobra'), ('SVC', 'Salvadoran Colón'), ('SYP', 'Syrian Pound'), ('SZL', 'Swazi Lilangeni'), ('THB', 'Thai Baht'), ('TJS', 'Tajikistani Somoni'), ('TMT', 'Turkmenistani Manat'), ('TND', 'Tunisian Dinar'), ('TOP', "Tongan Pa'anga"), ('TRY', 'Turkish Lira'), ('TTD', 'Trinidad and Tobago Dollar'), ('TWD', 'New Taiwan Dollar'), ('TZS', 'Tanzanian Shilling'), ('UAH', 'Ukrainian Hryvnia'), ('UGX', 'Ugandan Shilling'), ('USD', 'United States Dollar'), ('UYU', 'Uruguayan Peso'), ('UZS', 'Uzbekistan Som'), ('VEF', 'Venezuelan Bolívar Fuerte'), ('VND', 'Vietnamese Dong'), ('VUV', 'Vanuatu Vatu'), ('WST', 'Samoan Tala'), ('XAF', 'CFA Franc BEAC'), ('XAG', 'Silver Ounce'), ('XAU', 'Gold Ounce'), ('XCD', 'East Caribbean Dollar'), ('XDR', 'Special Drawing Rights'), ('XOF', 'CFA Franc BCEAO'), ('XPD', 'Palladium Ounce'), ('XPF', 'CFP Franc'), ('XPT', 'Platinum Ounce'), ('YER', 'Yemeni Rial'), ('ZAR', 'South African Rand'), ('ZMK', 'Zambian Kwacha (pre-2013)'), ('ZMW', 'Zambian Kwacha'), ('ZWL', 'Zimbabwean Dollar')], db_index=True, max_length=3, unique=True, verbose_name='To'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /django_prices_openexchangerates/migrations/0005_auto_20190124_1008.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.5 on 2019-01-24 10:08 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('django_prices_openexchangerates', '0004_auto_20170316_0944'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='conversionrate', 15 | name='to_currency', 16 | field=models.CharField(choices=[('AED', 'United Arab Emirates Dirham'), ('AFN', 'Afghan Afghani'), ('ALL', 'Albanian Lek'), ('AMD', 'Armenian Dram'), ('ANG', 'Netherlands Antillean Guilder'), ('AOA', 'Angolan Kwanza'), ('ARS', 'Argentine Peso'), ('AUD', 'Australian Dollar'), ('AWG', 'Aruban Florin'), ('AZN', 'Azerbaijani Manat'), ('BAM', 'Bosnia-Herzegovina Convertible Mark'), ('BBD', 'Barbadian Dollar'), ('BDT', 'Bangladeshi Taka'), ('BGN', 'Bulgarian Lev'), ('BHD', 'Bahraini Dinar'), ('BIF', 'Burundian Franc'), ('BMD', 'Bermudan Dollar'), ('BND', 'Brunei Dollar'), ('BOB', 'Bolivian Boliviano'), ('BRL', 'Brazilian Real'), ('BSD', 'Bahamian Dollar'), ('BTC', 'Bitcoin'), ('BTN', 'Bhutanese Ngultrum'), ('BWP', 'Botswanan Pula'), ('BYN', 'Belarusian Ruble'), ('BZD', 'Belize Dollar'), ('CAD', 'Canadian Dollar'), ('CDF', 'Congolese Franc'), ('CHF', 'Swiss Franc'), ('CLF', 'Chilean Unit of Account (UF)'), ('CLP', 'Chilean Peso'), ('CNH', 'Chinese Yuan (Offshore)'), ('CNY', 'Chinese Yuan'), ('COP', 'Colombian Peso'), ('CRC', 'Costa Rican Colón'), ('CUC', 'Cuban Convertible Peso'), ('CUP', 'Cuban Peso'), ('CVE', 'Cape Verdean Escudo'), ('CZK', 'Czech Republic Koruna'), ('DJF', 'Djiboutian Franc'), ('DKK', 'Danish Krone'), ('DOP', 'Dominican Peso'), ('DZD', 'Algerian Dinar'), ('EGP', 'Egyptian Pound'), ('ERN', 'Eritrean Nakfa'), ('ETB', 'Ethiopian Birr'), ('EUR', 'Euro'), ('FJD', 'Fijian Dollar'), ('FKP', 'Falkland Islands Pound'), ('GBP', 'British Pound Sterling'), ('GEL', 'Georgian Lari'), ('GGP', 'Guernsey Pound'), ('GHS', 'Ghanaian Cedi'), ('GIP', 'Gibraltar Pound'), ('GMD', 'Gambian Dalasi'), ('GNF', 'Guinean Franc'), ('GTQ', 'Guatemalan Quetzal'), ('GYD', 'Guyanaese Dollar'), ('HKD', 'Hong Kong Dollar'), ('HNL', 'Honduran Lempira'), ('HRK', 'Croatian Kuna'), ('HTG', 'Haitian Gourde'), ('HUF', 'Hungarian Forint'), ('IDR', 'Indonesian Rupiah'), ('ILS', 'Israeli New Sheqel'), ('IMP', 'Manx pound'), ('INR', 'Indian Rupee'), ('IQD', 'Iraqi Dinar'), ('IRR', 'Iranian Rial'), ('ISK', 'Icelandic Króna'), ('JEP', 'Jersey Pound'), ('JMD', 'Jamaican Dollar'), ('JOD', 'Jordanian Dinar'), ('JPY', 'Japanese Yen'), ('KES', 'Kenyan Shilling'), ('KGS', 'Kyrgystani Som'), ('KHR', 'Cambodian Riel'), ('KMF', 'Comorian Franc'), ('KPW', 'North Korean Won'), ('KRW', 'South Korean Won'), ('KWD', 'Kuwaiti Dinar'), ('KYD', 'Cayman Islands Dollar'), ('KZT', 'Kazakhstani Tenge'), ('LAK', 'Laotian Kip'), ('LBP', 'Lebanese Pound'), ('LKR', 'Sri Lankan Rupee'), ('LRD', 'Liberian Dollar'), ('LSL', 'Lesotho Loti'), ('LYD', 'Libyan Dinar'), ('MAD', 'Moroccan Dirham'), ('MDL', 'Moldovan Leu'), ('MGA', 'Malagasy Ariary'), ('MKD', 'Macedonian Denar'), ('MMK', 'Myanma Kyat'), ('MNT', 'Mongolian Tugrik'), ('MOP', 'Macanese Pataca'), ('MRO', 'Mauritanian Ouguiya (pre-2018)'), ('MRU', 'Mauritanian Ouguiya'), ('MUR', 'Mauritian Rupee'), ('MVR', 'Maldivian Rufiyaa'), ('MWK', 'Malawian Kwacha'), ('MXN', 'Mexican Peso'), ('MYR', 'Malaysian Ringgit'), ('MZN', 'Mozambican Metical'), ('NAD', 'Namibian Dollar'), ('NGN', 'Nigerian Naira'), ('NIO', 'Nicaraguan Córdoba'), ('NOK', 'Norwegian Krone'), ('NPR', 'Nepalese Rupee'), ('NZD', 'New Zealand Dollar'), ('OMR', 'Omani Rial'), ('PAB', 'Panamanian Balboa'), ('PEN', 'Peruvian Nuevo Sol'), ('PGK', 'Papua New Guinean Kina'), ('PHP', 'Philippine Peso'), ('PKR', 'Pakistani Rupee'), ('PLN', 'Polish Zloty'), ('PYG', 'Paraguayan Guarani'), ('QAR', 'Qatari Rial'), ('RON', 'Romanian Leu'), ('RSD', 'Serbian Dinar'), ('RUB', 'Russian Ruble'), ('RWF', 'Rwandan Franc'), ('SAR', 'Saudi Riyal'), ('SBD', 'Solomon Islands Dollar'), ('SCR', 'Seychellois Rupee'), ('SDG', 'Sudanese Pound'), ('SEK', 'Swedish Krona'), ('SGD', 'Singapore Dollar'), ('SHP', 'Saint Helena Pound'), ('SLL', 'Sierra Leonean Leone'), ('SOS', 'Somali Shilling'), ('SRD', 'Surinamese Dollar'), ('SSP', 'South Sudanese Pound'), ('STD', 'São Tomé and Príncipe Dobra (pre-2018)'), ('STN', 'São Tomé and Príncipe Dobra'), ('SVC', 'Salvadoran Colón'), ('SYP', 'Syrian Pound'), ('SZL', 'Swazi Lilangeni'), ('THB', 'Thai Baht'), ('TJS', 'Tajikistani Somoni'), ('TMT', 'Turkmenistani Manat'), ('TND', 'Tunisian Dinar'), ('TOP', "Tongan Pa'anga"), ('TRY', 'Turkish Lira'), ('TTD', 'Trinidad and Tobago Dollar'), ('TWD', 'New Taiwan Dollar'), ('TZS', 'Tanzanian Shilling'), ('UAH', 'Ukrainian Hryvnia'), ('UGX', 'Ugandan Shilling'), ('USD', 'United States Dollar'), ('UYU', 'Uruguayan Peso'), ('UZS', 'Uzbekistan Som'), ('VEF', 'Venezuelan Bolívar Fuerte (Old)'), ('VES', 'Venezuelan Bolívar Soberano'), ('VND', 'Vietnamese Dong'), ('VUV', 'Vanuatu Vatu'), ('WST', 'Samoan Tala'), ('XAF', 'CFA Franc BEAC'), ('XAG', 'Silver Ounce'), ('XAU', 'Gold Ounce'), ('XCD', 'East Caribbean Dollar'), ('XDR', 'Special Drawing Rights'), ('XOF', 'CFA Franc BCEAO'), ('XPD', 'Palladium Ounce'), ('XPF', 'CFP Franc'), ('XPT', 'Platinum Ounce'), ('YER', 'Yemeni Rial'), ('ZAR', 'South African Rand'), ('ZMW', 'Zambian Kwacha'), ('ZWL', 'Zimbabwean Dollar')], db_index=True, max_length=3, unique=True, verbose_name='To'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /django_prices_openexchangerates/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirumee/django-prices-openexchangerates/1d91c4b376894a72992b37893b582aee84f6a0f9/django_prices_openexchangerates/migrations/__init__.py -------------------------------------------------------------------------------- /django_prices_openexchangerates/models.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | from django.conf import settings 4 | from django.core.exceptions import ValidationError 5 | from django.core.cache import cache 6 | from django.db import models 7 | from django.utils.translation import gettext_lazy as _ 8 | 9 | from .currencies import CURRENCIES 10 | 11 | BASE_CURRENCY = getattr(settings, "OPENEXCHANGERATES_BASE_CURRENCY", "USD") 12 | CACHE_KEY = getattr( 13 | settings, "OPENEXCHANGERATES_CACHE_KEY", "openexchangerates_conversion_rates" 14 | ) 15 | CACHE_TIME = getattr(settings, "OPENEXCHANGERATES_CACHE_TTL", 60 * 60) 16 | 17 | 18 | def get_rates(qs, force_refresh=False): 19 | conversion_rates = cache.get(CACHE_KEY) 20 | if not conversion_rates or force_refresh: 21 | conversion_rates = {rate.to_currency: rate for rate in qs} 22 | cache.set(CACHE_KEY, conversion_rates, CACHE_TIME) 23 | return conversion_rates 24 | 25 | 26 | class CachingManager(models.Manager): 27 | def get_rate(self, to_currency): # noqa 28 | all_rates = get_rates(self.all()) 29 | try: 30 | return all_rates[to_currency] 31 | except KeyError: 32 | msg = "ConversionRate for %s does not exist" % to_currency 33 | raise ConversionRate.DoesNotExist(msg) 34 | 35 | 36 | class ConversionRate(models.Model): 37 | 38 | base_currency = BASE_CURRENCY 39 | 40 | to_currency = models.CharField( 41 | _("To"), max_length=3, db_index=True, choices=CURRENCIES, unique=True 42 | ) 43 | 44 | rate = models.DecimalField(_("Conversion rate"), max_digits=20, decimal_places=12) 45 | 46 | modified_at = models.DateTimeField(auto_now=True) 47 | 48 | objects = CachingManager() 49 | 50 | class Meta: 51 | ordering = ["to_currency"] 52 | 53 | def save(self, *args, **kwargs): # noqa 54 | """Save the model instance but only on successful validation.""" 55 | self.full_clean() 56 | super(ConversionRate, self).save(*args, **kwargs) 57 | 58 | def clean(self): # noqa 59 | if self.rate <= Decimal(0): 60 | raise ValidationError("Conversion rate has to be positive") 61 | if self.base_currency == self.to_currency: 62 | raise ValidationError("Can't set a conversion rate for the same currency") 63 | super(ConversionRate, self).clean() 64 | 65 | def __str__(self): # noqa 66 | return "1 %s = %.04f %s" % (self.base_currency, self.rate, self.to_currency) 67 | 68 | def __repr__(self): # noqa 69 | format_template = ( 70 | "ConversionRate(pk=%r, base_currency=%r, to_currency=%r, rate=%r)" 71 | ) 72 | return format_template % ( 73 | self.pk, 74 | self.base_currency, 75 | self.to_currency, 76 | self.rate, 77 | ) 78 | -------------------------------------------------------------------------------- /django_prices_openexchangerates/tasks.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | import requests 4 | import logging 5 | from django.conf import settings 6 | from django.core.exceptions import ImproperlyConfigured 7 | 8 | from .models import ConversionRate, get_rates 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | ENDPOINT_LATEST = 'http://openexchangerates.org/api/latest.json' 13 | BASE_CURRENCY = getattr(settings, 'OPENEXCHANGERATES_BASE_CURRENCY', 'USD') 14 | 15 | try: 16 | API_KEY = settings.OPENEXCHANGERATES_API_KEY 17 | except AttributeError: 18 | raise ImproperlyConfigured('OPENEXCHANGERATES_API_KEY is required') 19 | 20 | 21 | def extract_rate(rates, currency): 22 | base_rate = rates[BASE_CURRENCY] 23 | return rates[currency] / base_rate 24 | 25 | 26 | def get_latest_exchange_rates(): 27 | response = requests.get( 28 | ENDPOINT_LATEST, params={'app_id': API_KEY, 'base': BASE_CURRENCY}) 29 | response.raise_for_status() 30 | return response.json(parse_int=Decimal, parse_float=Decimal)['rates'] 31 | 32 | 33 | def update_conversion_rates(): 34 | exchange_rates = get_latest_exchange_rates() 35 | conversion_rates = ConversionRate.objects.all() 36 | for conversion_rate in conversion_rates: 37 | new_exchange_rate = extract_rate( 38 | exchange_rates, conversion_rate.to_currency) 39 | conversion_rate.rate = new_exchange_rate 40 | conversion_rate.save(update_fields=['rate', 'modified_at']) 41 | get_rates(ConversionRate.objects.all(), force_refresh=True) 42 | return conversion_rates 43 | 44 | 45 | def create_conversion_rates(): 46 | exchange_rates = get_latest_exchange_rates() 47 | for currency in exchange_rates: 48 | if currency == BASE_CURRENCY: 49 | continue 50 | rate = extract_rate(exchange_rates, currency) 51 | try: 52 | conversion_rate, _ = ConversionRate.objects.get_or_create( 53 | to_currency=currency, rate=rate) 54 | except Exception: 55 | logger.exception( 56 | 'Unable to create ConversionRate', 57 | extra={'currency': currency, 'rate': rate}) 58 | else: 59 | yield conversion_rate 60 | -------------------------------------------------------------------------------- /django_prices_openexchangerates/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirumee/django-prices-openexchangerates/1d91c4b376894a72992b37893b582aee84f6a0f9/django_prices_openexchangerates/templatetags/__init__.py -------------------------------------------------------------------------------- /django_prices_openexchangerates/templatetags/prices_multicurrency.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar 2 | 3 | from django.template import Library 4 | from prices import Money, MoneyRange, TaxedMoney, TaxedMoneyRange 5 | 6 | from .. import exchange_currency 7 | 8 | T = TypeVar('T', Money, MoneyRange, TaxedMoney, TaxedMoneyRange) 9 | 10 | register = Library() 11 | 12 | 13 | @register.filter 14 | def in_currency(base: T, currency: str) -> T: 15 | converted_base = exchange_currency(base, currency) 16 | return converted_base.quantize() 17 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | test=pytest -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | from setuptools import setup 3 | 4 | CLASSIFIERS = [ 5 | "Environment :: Web Environment", 6 | "Intended Audience :: Developers", 7 | "License :: OSI Approved :: BSD License", 8 | "Operating System :: OS Independent", 9 | "Programming Language :: Python", 10 | "Programming Language :: Python :: 3.7", 11 | "Programming Language :: Python :: 3.8", 12 | "Programming Language :: Python :: 3.9", 13 | "Programming Language :: Python :: 3.10", 14 | "Topic :: Internet :: WWW/HTTP", 15 | "Topic :: Internet :: WWW/HTTP :: Dynamic Content", 16 | "Topic :: Software Development :: Libraries :: Application Frameworks", 17 | "Topic :: Software Development :: Libraries :: Python Modules", 18 | ] 19 | 20 | with open("README.md", "r", encoding="utf8") as fh: 21 | long_description = fh.read() 22 | 23 | setup( 24 | name="django-prices-openexchangerates", 25 | author="Mirumee Software", 26 | author_email="hello@mirumee.com", 27 | description="openexchangerates.org support for django-prices", 28 | license="BSD", 29 | version="1.2.0", 30 | long_description=long_description, 31 | long_description_content_type="text/markdown", 32 | url="https://github.com/mirumee/django-prices-openexchangerates", 33 | packages=[ 34 | "django_prices_openexchangerates", 35 | "django_prices_openexchangerates.management", 36 | "django_prices_openexchangerates.management.commands", 37 | "django_prices_openexchangerates.migrations", 38 | "django_prices_openexchangerates.templatetags", 39 | ], 40 | include_package_data=True, 41 | classifiers=CLASSIFIERS, 42 | install_requires=["Django>=3.0", "django-prices>=1.0.0", "prices>=1.0.0"], 43 | platforms=["any"], 44 | tests_require=["mock==1.0.1", "pytest"], 45 | zip_safe=False, 46 | ) 47 | -------------------------------------------------------------------------------- /test_settings.py: -------------------------------------------------------------------------------- 1 | DATABASES = { 2 | 'default': { 3 | 'ENGINE': 'django.db.backends.sqlite3', 4 | 'NAME': ':memory:'}} 5 | 6 | SECRET_KEY = 'irrelevant' 7 | DEFAULT_CURRENCY = 'USD' 8 | AVAILABLE_PURCHASE_CURRENCIES = ['USD', 'EUR', 'GBP'] 9 | OPENEXCHANGE_BASE_CURRENCY = 'USD' 10 | INSTALLED_APPS = [ 11 | 'django_prices_openexchangerates' 12 | ] 13 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirumee/django-prices-openexchangerates/1d91c4b376894a72992b37893b582aee84f6a0f9/tests/__init__.py -------------------------------------------------------------------------------- /tests/settings.py: -------------------------------------------------------------------------------- 1 | SECRET_KEY = 'irrelevant' 2 | 3 | INSTALLED_APPS = ['django_prices_openexchangerates', 'tests'] 4 | 5 | TEMPLATES = [ 6 | { 7 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 8 | 'APP_DIRS': True}] 9 | 10 | DATABASES = { 11 | 'default': { 12 | 'ENGINE': 'django.db.backends.sqlite3', 13 | 'NAME': 'database.sqlite'}} 14 | -------------------------------------------------------------------------------- /tests/test_openexchangerates.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import functools 3 | from decimal import Decimal 4 | 5 | # import mock 6 | import pytest 7 | from prices import ( 8 | Money, TaxedMoney, MoneyRange, TaxedMoneyRange, percentage_discount) 9 | from django_prices_openexchangerates import exchange_currency 10 | from django_prices_openexchangerates.models import ConversionRate, get_rates 11 | from django_prices_openexchangerates.templatetags import prices_multicurrency 12 | 13 | 14 | RATES = { 15 | 'EUR': Decimal(2), 16 | 'GBP': Decimal(4), 17 | 'BTC': Decimal(10)} 18 | 19 | 20 | @pytest.fixture 21 | def base_currency(db, settings): 22 | settings.OPENEXCHANGERATES_BASE_CURRENCY = 'BTC' 23 | 24 | 25 | @pytest.fixture(autouse=True) 26 | def conversion_rates(db): 27 | rates = [] 28 | for currency, value in RATES.items(): 29 | rate = ConversionRate.objects.create( 30 | to_currency=currency, rate=RATES[currency]) 31 | rates.append(rate) 32 | return rates 33 | 34 | 35 | def test_conversionrate__str_repr(conversion_rates): 36 | obj = ConversionRate.objects.get(to_currency='EUR') 37 | 38 | assert str(obj) == '1 USD = 2.0000 EUR' 39 | 40 | obj_repr = repr(obj) 41 | assert 'ConversionRate' in obj_repr 42 | assert 'pk={},'.format(obj.pk) in obj_repr 43 | assert "base_currency='USD'," in obj_repr 44 | assert "to_currency='EUR'," in obj_repr 45 | 46 | 47 | def test_the_same_currency_uses_no_conversion(): 48 | value = Money(10, currency='EUR') 49 | converted_value = exchange_currency(value, 'EUR') 50 | assert converted_value == value 51 | 52 | 53 | def test_base_currency_to_another(): 54 | value = Money(10, currency='USD') 55 | converted_value = exchange_currency(value, 'EUR') 56 | assert converted_value == Money(20, currency='EUR') 57 | 58 | 59 | def test_convert_other_currency_to_base_currency(): 60 | value = Money(20, currency='EUR') 61 | converted_value = exchange_currency(value, 'USD') 62 | assert converted_value == Money(10, currency='USD') 63 | 64 | 65 | def test_two_base_currencies_the_same_currency_uses_no_conversion(): 66 | value = Money(10, currency='USD') 67 | converted_value = exchange_currency(value, 'USD') 68 | assert converted_value == value 69 | 70 | 71 | def test_convert_two_non_base_currencies(): 72 | value = Money(20, currency='EUR') 73 | converted_value = exchange_currency(value, 'GBP') 74 | assert converted_value == Money(40, currency='GBP') 75 | 76 | 77 | def test_exchange_currency_uses_passed_conversion_rate(): 78 | value = Money(10, currency='USD') 79 | custom_rate = Decimal(5) 80 | 81 | converted_value = exchange_currency( 82 | value, 'GBP', conversion_rate=custom_rate) 83 | assert converted_value == Money(50, currency='GBP') 84 | 85 | 86 | def test_two_base_currencies_convert_price_uses_passed_conversion_rate(): 87 | value = Money(10, currency='USD') 88 | custom_rate = Decimal('4.2') 89 | 90 | converted_value = exchange_currency( 91 | value, 'GBP', conversion_rate=custom_rate) 92 | assert converted_value == Money(42, 'GBP') 93 | 94 | 95 | def test_exchange_currency_for_money_range(): 96 | value = MoneyRange(Money(10, 'USD'), Money(15, 'USD')) 97 | 98 | value_converted = exchange_currency(value, 'GBP') 99 | assert value_converted.currency == 'GBP' 100 | assert value_converted.start == Money(40, currency='GBP') 101 | assert value_converted.stop == Money(60, currency='GBP') 102 | 103 | 104 | def test_exchange_currency_for_money_range_uses_passed_conversion_rate(): 105 | value = MoneyRange(Money(10, 'USD'), Money(15, 'USD')) 106 | custom_rate = Decimal(2) 107 | 108 | value_converted = exchange_currency( 109 | value, 'GBP', conversion_rate=custom_rate) 110 | assert value_converted.currency == 'GBP' 111 | assert value_converted.start == Money(20, currency='GBP') 112 | assert value_converted.stop == Money(30, currency='GBP') 113 | 114 | 115 | def test_exchange_currency_for_taxed_money(): 116 | value = TaxedMoney(Money(10, 'USD'), Money(12, 'USD')) 117 | 118 | value_converted = exchange_currency(value, 'GBP') 119 | assert value_converted.currency == 'GBP' 120 | assert value_converted.net == Money(40, currency='GBP') 121 | assert value_converted.gross == Money(48, currency='GBP') 122 | 123 | 124 | def test_exchange_currency_for_taxed_money_uses_passed_conversion_rate(): 125 | value = TaxedMoney(Money(10, 'USD'), Money(12, 'USD')) 126 | custom_rate = Decimal(2) 127 | 128 | value_converted = exchange_currency( 129 | value, 'GBP', conversion_rate=custom_rate) 130 | assert value_converted.currency == 'GBP' 131 | assert value_converted.net == Money(20, currency='GBP') 132 | assert value_converted.gross == Money(24, currency='GBP') 133 | 134 | 135 | def test_exchange_currency_for_taxed_money_range(): 136 | value = TaxedMoneyRange( 137 | TaxedMoney(Money(10, 'USD'), Money(12, 'USD')), 138 | TaxedMoney(Money(20, 'USD'), Money(24, 'USD'))) 139 | 140 | value_converted = exchange_currency(value, 'GBP') 141 | assert value_converted.currency == 'GBP' 142 | assert value_converted.start.currency == 'GBP' 143 | assert value_converted.start.net == Money(40, currency='GBP') 144 | assert value_converted.start.gross == Money(48, currency='GBP') 145 | assert value_converted.stop.currency == 'GBP' 146 | assert value_converted.stop.net == Money(80, currency='GBP') 147 | assert value_converted.stop.gross == Money(96, currency='GBP') 148 | 149 | 150 | def test_exchange_currency_for_taxed_money_range_uses_passed_conversion_rate(): 151 | value = TaxedMoneyRange( 152 | TaxedMoney(Money(10, 'USD'), Money(12, 'USD')), 153 | TaxedMoney(Money(20, 'USD'), Money(24, 'USD'))) 154 | custom_rate = Decimal(2) 155 | 156 | value_converted = exchange_currency( 157 | value, 'GBP', conversion_rate=custom_rate) 158 | assert value_converted.currency == 'GBP' 159 | assert value_converted.start.currency == 'GBP' 160 | assert value_converted.start.net.currency == 'GBP' 161 | assert value_converted.start.net.amount == 20 162 | assert value_converted.start.gross.currency == 'GBP' 163 | assert value_converted.start.gross.amount == 24 164 | assert value_converted.stop.currency == 'GBP' 165 | assert value_converted.stop.net.currency == 'GBP' 166 | assert value_converted.stop.net.amount == 40 167 | assert value_converted.stop.gross.currency == 'GBP' 168 | assert value_converted.stop.gross.amount == 48 169 | 170 | 171 | def test_exchange_currency_raises_for_nonsupported_type(): 172 | with pytest.raises(TypeError): 173 | class PseudoMoneyType: 174 | currency = 'USD' 175 | converted_value = exchange_currency(PseudoMoneyType(), 'GBP') 176 | with pytest.raises(AttributeError): 177 | converted_value = exchange_currency('str', 'GBP') 178 | 179 | 180 | def test_template_filter_money_in_currency(): 181 | value = Money(Decimal('1.23456789'), currency='USD') 182 | result = prices_multicurrency.in_currency(value, currency='EUR') 183 | assert result == Money('2.47', 'EUR') 184 | 185 | 186 | def test_get_rates_caches_results(conversion_rates): 187 | result = get_rates(qs=conversion_rates) 188 | assert all(currency in result.keys() for currency in ['BTC', 'GBP', 'EUR']) 189 | 190 | 191 | def test_get_rates_force_update_cache(conversion_rates): 192 | expected_cache_content = { 193 | rate.to_currency: rate for rate in conversion_rates} 194 | rates = get_rates(qs=conversion_rates, force_refresh=True) 195 | assert rates == expected_cache_content 196 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py37-django3 4 | py{38,39,310}-django{3,4} 5 | py{38,39,310}-django_master 6 | 7 | [gh-actions] 8 | python = 9 | 3.7: py37 10 | 3.8: py38 11 | 3.9: py39 12 | 3.10: py310 13 | 14 | [testenv] 15 | pip_pre = true 16 | deps = 17 | django3: Django>=3.0,<4 18 | django4: Django>=4.0,<5 19 | pytest 20 | pytest-cov 21 | pytest-django 22 | commands = 23 | django_master: pip install https://github.com/django/django/archive/master.tar.gz 24 | pytest --cov --cov-report= 25 | setenv = 26 | PYTHONPATH=. 27 | 28 | [travis] 29 | python = 30 | 3.7: py37 31 | 3.8: py38 32 | 3.9: py39 33 | 3.10: py310 34 | unignore_outcomes = True 35 | 36 | [travis:env] 37 | DJANGO = 38 | 3: django3 39 | 4: django4 40 | master: django_master 41 | 42 | [pytest] 43 | testpaths = tests 44 | DJANGO_SETTINGS_MODULE = tests.settings 45 | --------------------------------------------------------------------------------