From bb880aab5e5c3414775bdc3943a63038f4b83bc4 Mon Sep 17 00:00:00 2001 From: Noman Bukhari Date: Sat, 11 Nov 2023 20:49:43 -0800 Subject: [PATCH 1/2] Enable all currencies supported by Stripe --- saas/forms.py | 5 +- saas/humanize.py | 11 ++- saas/settings.py | 4 + saas/static/data/currencies.json | 139 +++++++++++++++++++++++++++++++ saas/utils.py | 37 +++++++- 5 files changed, 186 insertions(+), 10 deletions(-) create mode 100644 saas/static/data/currencies.json diff --git a/saas/forms.py b/saas/forms.py index 403517a05..d1314b0ed 100644 --- a/saas/forms.py +++ b/saas/forms.py @@ -40,7 +40,7 @@ from . import settings from .compat import gettext_lazy as _ from .models import AdvanceDiscount, Plan, Subscription -from .utils import get_organization_model +from .utils import get_organization_model, get_currency_choices #pylint: disable=no-member @@ -260,8 +260,7 @@ class PlanForm(forms.ModelForm): (slugify(choice[1]), choice[1]) for choice in Plan.INTERVAL_CHOICES]) renewal_type = forms.ChoiceField(choices=[ (slugify(choice[1]), choice[1]) for choice in Plan.RENEWAL_CHOICES]) - unit = forms.ChoiceField(choices=( - ('usd', 'usd'), ('cad', 'cad'), ('eur', 'eur'))) + unit = forms.ChoiceField(choices=get_currency_choices()) period_amount = forms.DecimalField(max_digits=7, decimal_places=2) advance_discount_type = forms.ChoiceField(choices=[ (slugify(choice[1]), choice[1]) diff --git a/saas/humanize.py b/saas/humanize.py index e02a26a1c..ccb994ebc 100644 --- a/saas/humanize.py +++ b/saas/humanize.py @@ -28,6 +28,7 @@ from . import settings from .compat import gettext_lazy as _ +from .utils import get_currency_symbols # prevents an import loop with models.py HOURLY = 1 @@ -190,12 +191,10 @@ def as_money(value, currency=settings.DEFAULT_UNIT, negative_format="(%s)"): negative = True currency = currency[1:] currency = currency.lower() - if currency in ['usd', 'cad']: - unit_prefix = '$' - if currency != 'usd': - unit_suffix = ' %s' % currency - elif currency in ['eur']: - unit_prefix = '\u20ac' + currency_symbol = get_currency_symbols(currency) if currency \ + else None + if currency_symbol: + unit_prefix = currency_symbol else: unit_suffix = ' %s' % currency grouped = "" diff --git a/saas/settings.py b/saas/settings.py index 3cb411800..688fdc547 100644 --- a/saas/settings.py +++ b/saas/settings.py @@ -208,3 +208,7 @@ CONTRIBUTOR = _SETTINGS.get('CONTRIBUTOR') MANAGER = _SETTINGS.get('MANAGER') PROFILE_URL_KWARG = _SETTINGS.get('PROFILE_URL_KWARG') + +CURRENCY_JSON_PATH = os.path.join( + settings.BASE_DIR, 'saas', + 'static', 'data', 'currencies.json') diff --git a/saas/static/data/currencies.json b/saas/static/data/currencies.json new file mode 100644 index 000000000..da61189c7 --- /dev/null +++ b/saas/static/data/currencies.json @@ -0,0 +1,139 @@ +[ + {"cc":"USD","symbol":"$","name":"United States dollar"}, + {"cc":"CAD","symbol":"$","name":"Canadian dollar"}, + {"cc":"EUR","symbol":"\u20ac","name":"European Euro"}, + {"cc":"AED","symbol":"\u062f.\u0625;","name":"UAE dirham"}, + {"cc":"AFN","symbol":"Afs","name":"Afghan afghani"}, + {"cc":"ALL","symbol":"L","name":"Albanian lek"}, + {"cc":"AMD","symbol":"AMD","name":"Armenian dram"}, + {"cc":"ANG","symbol":"NA\u0192","name":"Netherlands Antillean gulden"}, + {"cc":"AOA","symbol":"Kz","name":"Angolan kwanza"}, + {"cc":"ARS","symbol":"$","name":"Argentine peso"}, + {"cc":"AUD","symbol":"$","name":"Australian dollar"}, + {"cc":"AWG","symbol":"\u0192","name":"Aruban florin"}, + {"cc":"AZN","symbol":"AZN","name":"Azerbaijani manat"}, + {"cc":"BAM","symbol":"KM","name":"Bosnia and Herzegovina konvertibilna marka"}, + {"cc":"BBD","symbol":"Bds$","name":"Barbadian dollar"}, + {"cc":"BDT","symbol":"\u09f3","name":"Bangladeshi taka"}, + {"cc":"BGN","symbol":"BGN","name":"Bulgarian lev"}, + {"cc":"BIF","symbol":"FBu","name":"Burundi franc"}, + {"cc":"BMD","symbol":"BD$","name":"Bermudian dollar"}, + {"cc":"BND","symbol":"B$","name":"Brunei dollar"}, + {"cc":"BOB","symbol":"Bs.","name":"Bolivian boliviano"}, + {"cc":"BRL","symbol":"R$","name":"Brazilian real"}, + {"cc":"BSD","symbol":"B$","name":"Bahamian dollar"}, + {"cc":"BTC","symbol":"\u20bf","name":"Bitcoin"}, + {"cc":"BTN","symbol":"Nu.","name":"Bhutanese ngultrum"}, + {"cc":"BWP","symbol":"P","name":"Botswana pula"}, + {"cc":"BYN","symbol":"Br","name":"Belarusian ruble"}, + {"cc":"BZD","symbol":"BZ$","name":"Belize dollar"}, + {"cc":"CDF","symbol":"F","name":"Congolese franc"}, + {"cc":"CHF","symbol":"Fr.","name":"Swiss franc"}, + {"cc":"CLP","symbol":"$","name":"Chilean peso"}, + {"cc":"CNY","symbol":"\u00a5","name":"Chinese/Yuan renminbi"}, + {"cc":"COP","symbol":"Col$","name":"Colombian peso"}, + {"cc":"CRC","symbol":"\u20a1","name":"Costa Rican colon"}, + {"cc":"CVE","symbol":"Esc","name":"Cape Verdean escudo"}, + {"cc":"CZK","symbol":"K\u010d","name":"Czech koruna"}, + {"cc":"DJF","symbol":"Fdj","name":"Djiboutian franc"}, + {"cc":"DKK","symbol":"Kr","name":"Danish krone"}, + {"cc":"DOP","symbol":"RD$","name":"Dominican peso"}, + {"cc":"DZD","symbol":"\u062f.\u062c","name":"Algerian dinar"}, + {"cc":"EGP","symbol":"\u00a3","name":"Egyptian pound"}, + {"cc":"ETB","symbol":"Br","name":"Ethiopian birr"}, + {"cc":"FJD","symbol":"FJ$","name":"Fijian dollar"}, + {"cc":"FKP","symbol":"\u00a3","name":"Falkland Islands pound"}, + {"cc":"GBP","symbol":"\u00a3","name":"British pound"}, + {"cc":"GEL","symbol":"GEL","name":"Georgian lari"}, + {"cc":"GIP","symbol":"\u00a3","name":"Gibraltar pound"}, + {"cc":"GMD","symbol":"D","name":"Gambian dalasi"}, + {"cc":"GNF","symbol":"FG","name":"Guinean franc"}, + {"cc":"GTQ","symbol":"Q","name":"Guatemalan quetzal"}, + {"cc":"GYD","symbol":"GY$","name":"Guyanese dollar"}, + {"cc":"HKD","symbol":"HK$","name":"Hong Kong dollar"}, + {"cc":"HNL","symbol":"L","name":"Honduran lempira"}, + {"cc":"HTG","symbol":"G","name":"Haitian gourde"}, + {"cc":"HUF","symbol":"Ft","name":"Hungarian forint"}, + {"cc":"IDR","symbol":"Rp","name":"Indonesian rupiah"}, + {"cc":"ILS","symbol":"\u20aa","name":"Israeli new sheqel"}, + {"cc":"INR","symbol":"\u20B9","name":"Indian rupee"}, + {"cc":"ISK","symbol":"kr","name":"Icelandic kr\u00f3na"}, + {"cc":"JMD","symbol":"J$","name":"Jamaican dollar"}, + {"cc":"JPY","symbol":"\u00a5","name":"Japanese yen"}, + {"cc":"KES","symbol":"KSh","name":"Kenyan shilling"}, + {"cc":"KGS","symbol":"\u0441\u043e\u043c","name":"Kyrgyzstani som"}, + {"cc":"KHR","symbol":"\u17db","name":"Cambodian riel"}, + {"cc":"KMF","symbol":"KMF","name":"Comorian franc"}, + {"cc":"KRW","symbol":"W","name":"South Korean won"}, + {"cc":"KYD","symbol":"KY$","name":"Cayman Islands dollar"}, + {"cc":"KZT","symbol":"T","name":"Kazakhstani tenge"}, + {"cc":"LAK","symbol":"KN","name":"Lao kip"}, + {"cc":"LBP","symbol":"\u00a3","name":"Lebanese lira"}, + {"cc":"LKR","symbol":"Rs","name":"Sri Lankan rupee"}, + {"cc":"LRD","symbol":"L$","name":"Liberian dollar"}, + {"cc":"LSL","symbol":"M","name":"Lesotho loti"}, + {"cc":"MAD","symbol":"MAD","name":"Moroccan dirham"}, + {"cc":"MDL","symbol":"MDL","name":"Moldovan leu"}, + {"cc":"MGA","symbol":"FMG","name":"Malagasy ariary"}, + {"cc":"MKD","symbol":"MKD","name":"Macedonian denar"}, + {"cc":"MMK","symbol":"K","name":"Myanma kyat"}, + {"cc":"MNT","symbol":"\u20ae","name":"Mongolian tugrik"}, + {"cc":"MOP","symbol":"P","name":"Macanese pataca"}, + {"cc":"MRO","symbol":"UM","name":"Mauritanian ouguiya"}, + {"cc":"MUR","symbol":"Rs","name":"Mauritian rupee"}, + {"cc":"MVR","symbol":"Rf","name":"Maldivian rufiyaa"}, + {"cc":"MWK","symbol":"MK","name":"Malawian kwacha"}, + {"cc":"MXN","symbol":"$","name":"Mexican peso"}, + {"cc":"MYR","symbol":"RM","name":"Malaysian ringgit"}, + {"cc":"MZN","symbol":"MT","name":"Mozambican metical"}, + {"cc":"NAD","symbol":"N$","name":"Namibian dollar"}, + {"cc":"NGN","symbol":"\u20a6","name":"Nigerian naira"}, + {"cc":"NIO","symbol":"C$","name":"Nicaraguan c\u00f3rdoba"}, + {"cc":"NOK","symbol":"kr","name":"Norwegian krone"}, + {"cc":"NPR","symbol":"NRs","name":"Nepalese rupee"}, + {"cc":"NZD","symbol":"NZ$","name":"New Zealand dollar"}, + {"cc":"PAB","symbol":"B./","name":"Panamanian balboa"}, + {"cc":"PEN","symbol":"S/.","name":"Peruvian nuevo sol"}, + {"cc":"PGK","symbol":"K","name":"Papua New Guinean kina"}, + {"cc":"PHP","symbol":"\u20b1","name":"Philippine peso"}, + {"cc":"PKR","symbol":"Rs.","name":"Pakistani rupee"}, + {"cc":"PLN","symbol":"z\u0142","name":"Polish zloty"}, + {"cc":"PYG","symbol":"\u20b2","name":"Paraguayan guarani"}, + {"cc":"QAR","symbol":"QR","name":"Qatari riyal"}, + {"cc":"RON","symbol":"L","name":"Romanian leu"}, + {"cc":"RSD","symbol":"din.","name":"Serbian dinar"}, + {"cc":"RUB","symbol":"\u20bd","name":"Russian ruble"}, + {"cc":"RWF","symbol":"FRw","name":"Rwandan franc"}, + {"cc":"SAR","symbol":"SR","name":"Saudi riyal"}, + {"cc":"SBD","symbol":"SI$","name":"Solomon Islands dollar"}, + {"cc":"SCR","symbol":"SR","name":"Seychellois rupee"}, + {"cc":"SEK","symbol":"kr","name":"Swedish krona"}, + {"cc":"SGD","symbol":"S$","name":"Singapore dollar"}, + {"cc":"SHP","symbol":"\u00a3","name":"Saint Helena pound"}, + {"cc":"SLE","symbol":"Le","name":"Sierra Leonean leone"}, + {"cc":"SOS","symbol":"Sh.","name":"Somali shilling"}, + {"cc":"SRD","symbol":"$","name":"Surinamese dollar"}, + {"cc":"STD","symbol":"Db","name":"São Tomé and Príncipe dobra"}, + {"cc":"SZL","symbol":"E","name":"Swazi lilangeni"}, + {"cc":"THB","symbol":"\u0e3f","name":"Thai baht"}, + {"cc":"TJS","symbol":"TJS","name":"Tajikistani somoni"}, + {"cc":"TOP","symbol":"T$","name":"Tongan Pa'anga"}, + {"cc":"TRY","symbol":"TRY","name":"Turkish new lira"}, + {"cc":"TTD","symbol":"TT$","name":"Trinidad and Tobago dollar"}, + {"cc":"TWD","symbol":"NT$","name":"New Taiwan dollar"}, + {"cc":"TZS","symbol":"TZS","name":"Tanzanian shilling"}, + {"cc":"UAH","symbol":"UAH","name":"Ukrainian hryvnia"}, + {"cc":"UGX","symbol":"USh","name":"Ugandan shilling"}, + {"cc":"UYU","symbol":"$U","name":"Uruguayan peso"}, + {"cc":"UZS","symbol":"UZS","name":"Uzbekistani som"}, + {"cc":"VND","symbol":"\u20ab","name":"Vietnamese dong"}, + {"cc":"VUV","symbol":"VT","name":"Vanuatu vatu"}, + {"cc":"WST","symbol":"WS$","name":"Samoan tala"}, + {"cc":"XAF","symbol":"CFA","name":"Central African CFA franc"}, + {"cc":"XCD","symbol":"EC$","name":"East Caribbean dollar"}, + {"cc":"XOF","symbol":"CFA","name":"West African CFA franc"}, + {"cc":"XPF","symbol":"F","name":"CFP franc"}, + {"cc":"YER","symbol":"YER","name":"Yemeni rial"}, + {"cc":"ZAR","symbol":"R","name":"South African rand"}, + {"cc":"ZMW","symbol":"ZK","name":"Zambian kwacha"} +] diff --git a/saas/utils.py b/saas/utils.py index 83d91c8c4..db677bd43 100644 --- a/saas/utils.py +++ b/saas/utils.py @@ -22,7 +22,7 @@ # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import datetime, inspect, random, re, sys +import datetime, inspect, random, re, sys, json from django.core.exceptions import NON_FIELD_ERRORS from django.core.files.storage import default_storage @@ -38,6 +38,7 @@ from pytz.tzinfo import DstTzInfo from .compat import get_model_class, gettext_lazy as _, import_string, six +from . import settings class SlugTitleMixin(object): @@ -402,3 +403,37 @@ def build_absolute_uri(request, location='/', provider=None, with_scheme=True): # If we don't have a `request` object, better to return a URL path # than throwing an error. return location + + +_currency_data = None + + +def load_currency_data(): + global _currency_data + if _currency_data is None: + try: + with open(settings.CURRENCY_JSON_PATH, 'r') as file: + currency_list = json.load(file) + _currency_data = {currency['cc']: currency for + currency in currency_list} + except FileNotFoundError: + raise + return _currency_data + + +def get_currency_symbols(currency_code): + data = load_currency_data() + if data: + currency = data.get(currency_code.upper()) + if currency: + return currency['symbol'] + return None + + +def get_currency_choices(): + data = load_currency_data() + if data: + return [( + code.lower(), code.lower()) for + code, info in data.items()] + return [] From 4f54ff9544cabd44a88bfddcc94d4843dce22d87 Mon Sep 17 00:00:00 2001 From: Noman Bukhari Date: Mon, 13 Nov 2023 02:02:11 -0800 Subject: [PATCH 2/2] Update to use a Class instead of a global variable --- saas/forms.py | 4 ++-- saas/humanize.py | 4 ++-- saas/utils.py | 59 +++++++++++++++++++++++------------------------- 3 files changed, 32 insertions(+), 35 deletions(-) diff --git a/saas/forms.py b/saas/forms.py index d1314b0ed..5d4f392d5 100644 --- a/saas/forms.py +++ b/saas/forms.py @@ -40,7 +40,7 @@ from . import settings from .compat import gettext_lazy as _ from .models import AdvanceDiscount, Plan, Subscription -from .utils import get_organization_model, get_currency_choices +from .utils import get_organization_model, CurrencyDataLoader #pylint: disable=no-member @@ -260,7 +260,7 @@ class PlanForm(forms.ModelForm): (slugify(choice[1]), choice[1]) for choice in Plan.INTERVAL_CHOICES]) renewal_type = forms.ChoiceField(choices=[ (slugify(choice[1]), choice[1]) for choice in Plan.RENEWAL_CHOICES]) - unit = forms.ChoiceField(choices=get_currency_choices()) + unit = forms.ChoiceField(choices=CurrencyDataLoader.get_currency_choices()) period_amount = forms.DecimalField(max_digits=7, decimal_places=2) advance_discount_type = forms.ChoiceField(choices=[ (slugify(choice[1]), choice[1]) diff --git a/saas/humanize.py b/saas/humanize.py index ccb994ebc..c8a7a7632 100644 --- a/saas/humanize.py +++ b/saas/humanize.py @@ -28,7 +28,7 @@ from . import settings from .compat import gettext_lazy as _ -from .utils import get_currency_symbols +from .utils import CurrencyDataLoader # prevents an import loop with models.py HOURLY = 1 @@ -191,7 +191,7 @@ def as_money(value, currency=settings.DEFAULT_UNIT, negative_format="(%s)"): negative = True currency = currency[1:] currency = currency.lower() - currency_symbol = get_currency_symbols(currency) if currency \ + currency_symbol = CurrencyDataLoader.get_currency_symbols(currency) if currency \ else None if currency_symbol: unit_prefix = currency_symbol diff --git a/saas/utils.py b/saas/utils.py index db677bd43..695865727 100644 --- a/saas/utils.py +++ b/saas/utils.py @@ -404,36 +404,33 @@ def build_absolute_uri(request, location='/', provider=None, with_scheme=True): # than throwing an error. return location +class CurrencyDataLoader: + _currency_data = None -_currency_data = None - - -def load_currency_data(): - global _currency_data - if _currency_data is None: - try: - with open(settings.CURRENCY_JSON_PATH, 'r') as file: - currency_list = json.load(file) - _currency_data = {currency['cc']: currency for - currency in currency_list} - except FileNotFoundError: - raise - return _currency_data - - -def get_currency_symbols(currency_code): - data = load_currency_data() - if data: - currency = data.get(currency_code.upper()) - if currency: - return currency['symbol'] - return None - + @classmethod + def load_currency_data(cls): + if cls._currency_data is None: + try: + with open(settings.CURRENCY_JSON_PATH, 'r') as file: + currency_list = json.load(file) + cls._currency_data = {currency['cc']: + currency for currency in currency_list} + except FileNotFoundError: + raise + return cls._currency_data + + @classmethod + def get_currency_symbols(cls, currency_code): + data = cls.load_currency_data() + if data: + currency = data.get(currency_code.upper()) + if currency: + return currency['symbol'] + return None -def get_currency_choices(): - data = load_currency_data() - if data: - return [( - code.lower(), code.lower()) for - code, info in data.items()] - return [] + @classmethod + def get_currency_choices(cls): + data = cls.load_currency_data() + if data: + return [(code.lower(), code.lower()) for code in data.keys()] + return []