Skip to content

Commit

Permalink
Merge pull request #144 from /issues/140
Browse files Browse the repository at this point in the history
Issue #140 - configurable currency units and formatting
  • Loading branch information
jantman authored Oct 30, 2017
2 parents 33cc9a6 + ebdef45 commit 83541e0
Show file tree
Hide file tree
Showing 24 changed files with 210 additions and 56 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ Changelog
Unreleased Changes
------------------

* `PR #140 <https://github.com/jantman/biweeklybudget/issues/140>`_ - Support user-configurable currencies and currency formatting.
This isn't all-out localization, but adds ``CURRENCY_CODE`` and ``LOCALE_NAME`` configuration settings to control the currency symbol
and formatting used in the user interface and logs.
* `PR #141 <https://github.com/jantman/biweeklybudget/pull/141>`_ - Switch acceptance tests from PhantomJS to headless Chrome.
* Switch docs build screenshot script to use headless Chrome instead of PhantomJS.

Expand Down
6 changes: 6 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ application available to anything other than localhost, but if you do, you need
application is **not** designed to be accessible in any way to anyone other than authorized users (i.e. if you just serve it
over the web, someone *will* get your account numbers, or worse).

*Note:* Any potential users outside of the US should see the documentation section on
`Currency Formatting and Localization <http://biweeklybudget.readthedocs.io/en/latest/app_usage.html#currency-formatting-and-localization>`_;
the short version is that I've done my best to make this configurable, but as far as I know I'm the
only person using this software. If anyone else wants to use it and it doesn't work for your currency
or locale, let me know and I'll fix it.

Important Warning
+++++++++++++++++

Expand Down
6 changes: 4 additions & 2 deletions biweeklybudget/db_event_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@

from biweeklybudget.models.budget_model import Budget
from biweeklybudget.models.transaction import Transaction
from biweeklybudget.utils import fmt_currency

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -118,9 +119,10 @@ def handle_new_transaction(session):
old_amt = float(budg.current_balance)
budg.current_balance = old_amt - float(obj.actual_amount)
logger.info(
'New transaction (%s) for $%s against standing budget id=%s; '
'New transaction (%s) for %s against standing budget id=%s; '
'update budget current_balance from %s to %s', obj.description,
obj.actual_amount, budg.id, old_amt, budg.current_balance
fmt_currency(obj.actual_amount), budg.id, fmt_currency(old_amt),
fmt_currency(budg.current_balance)
)
session.add(budg)
updated += 1
Expand Down
3 changes: 0 additions & 3 deletions biweeklybudget/flaskapp/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
"""

import logging
import locale

from flask import Flask

Expand All @@ -49,8 +48,6 @@
logging.basicConfig(level=logging.DEBUG, format=format)
logger = logging.getLogger()

locale.setlocale(locale.LC_ALL, '')

fix_werkzeug_logger()

app = Flask(__name__)
Expand Down
18 changes: 17 additions & 1 deletion biweeklybudget/flaskapp/context_processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
################################################################################
"""

from babel.numbers import get_currency_symbol

from biweeklybudget.flaskapp.app import app
from biweeklybudget.flaskapp.notifications import NotificationsController
from biweeklybudget import settings as settingsmod
Expand All @@ -56,7 +58,7 @@ def settings():
"""
Add settings to template context for all templates.
:return: template context with notifications added
:return: template context with settings added
:rtype: dict
"""
return {'settings': {x: getattr(settingsmod, x) for x in dir(settingsmod)}}
Expand All @@ -73,3 +75,17 @@ def utilities():
def cast_float(x):
return float(x)
return dict(cast_float=cast_float)


@app.context_processor
def add_currency_symbol():
"""
Context processor to inject the proper currency symbol into the Jinja2
context as the "CURRENCY_SYM" variable.
:return: proper currency symbol for our locale and currency
:rtype: str
"""
return dict(CURRENCY_SYM=get_currency_symbol(
settingsmod.CURRENCY_CODE, locale=settingsmod.LOCALE_NAME
))
9 changes: 4 additions & 5 deletions biweeklybudget/flaskapp/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,10 @@
################################################################################
"""

from locale import currency
from jinja2.runtime import Undefined
from humanize import naturaltime

from biweeklybudget.utils import dtnow
from biweeklybudget.utils import dtnow, fmt_currency
from biweeklybudget.flaskapp.app import app
from biweeklybudget.models.account import AcctType

Expand Down Expand Up @@ -112,15 +111,15 @@ def ago_filter(dt):
@app.template_filter('dollars')
def dollars_filter(x):
"""
Format as USD currency.
Format as currency using :py:func:`~.utils.fmt_currency`.
:param x: dollar amount, int, float, decimal, etc.
:param x: currency amount, int, float, decimal, etc.
:return: formatted currency
:rtype: str
"""
if x == '' or x is None or isinstance(x, Undefined):
return ''
return currency(x, grouping=True)
return fmt_currency(x)


@app.template_filter('reddollars')
Expand Down
16 changes: 7 additions & 9 deletions biweeklybudget/flaskapp/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,9 @@

import logging
from sqlalchemy import func
from locale import currency

from biweeklybudget.db import db_session
from biweeklybudget.utils import dtnow
from biweeklybudget.utils import dtnow, fmt_currency
from biweeklybudget.models.account import Account
from biweeklybudget.models.budget_model import Budget
from biweeklybudget.models.ofx_transaction import OFXTransaction
Expand Down Expand Up @@ -193,14 +192,13 @@ def get_notifications():
'period remaining</a>; %s <a href="/reconcile">'
'unreconciled</a>)!'
'' % (
currency(accounts_bal, grouping=True),
currency(
(standing_bal + curr_pp + unrec_amt),
grouping=True
fmt_currency(accounts_bal),
fmt_currency(
(standing_bal + curr_pp + unrec_amt)
),
currency(standing_bal, grouping=True),
currency(curr_pp, grouping=True),
currency(unrec_amt, grouping=True)
fmt_currency(standing_bal),
fmt_currency(curr_pp),
fmt_currency(unrec_amt)
)
})
unreconciled_ofx = NotificationsController.num_unreconciled_ofx()
Expand Down
4 changes: 2 additions & 2 deletions biweeklybudget/flaskapp/static/js/budget_charts.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ $(function() {
pointSize: 2,
hideHover: 'auto',
resize: true,
preUnits: '$',
preUnits: CURRENCY_SYMBOL,
continuousLine: true
});
});
Expand All @@ -63,7 +63,7 @@ $(function() {
pointSize: 2,
hideHover: 'auto',
resize: true,
preUnits: '$',
preUnits: CURRENCY_SYMBOL,
continuousLine: true
});
});
Expand Down
4 changes: 2 additions & 2 deletions biweeklybudget/flaskapp/static/js/credit_payoffs.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function addIncrease(settings) {
s = s + ' , increase sum of monthly payments to ';
s = s + '<label for="payoff_increase_frm_' + idx + '_amt" class="control-label sr-only">Payment Increase ' + idx + ' Amount</label>';
s = s + '<div class="input-group">';
s = s + '<span class="input-group-addon">$</span><input class="form-control" id="payoff_increase_frm_' + idx + '_amt" name="payoff_increase_frm_' + idx + '_amt" type="text" size="8" style="width: auto;" onchange="setChanged()">';
s = s + '<span class="input-group-addon">' + CURRENCY_SYMBOL + '</span><input class="form-control" id="payoff_increase_frm_' + idx + '_amt" name="payoff_increase_frm_' + idx + '_amt" type="text" size="8" style="width: auto;" onchange="setChanged()">';
s = s + '</div> . (<a href="#" onclick="removeIncrease(' + idx + ')" id="rm_increase_' + idx + '_link">remove</a>)</div></form>';
s = s + '<!-- /#payoff_increase_frm_' + idx + ' -->';
$('#payoff_increase_forms').append(s);
Expand Down Expand Up @@ -104,7 +104,7 @@ function addOnetime(settings) {
s = s + ' , add ';
s = s + '<label for="payoff_onetime_frm_' + idx + '_amt" class="control-label sr-only">Onetime Payment ' + idx + ' Amount</label>';
s = s + '<div class="input-group">';
s = s + '<span class="input-group-addon">$</span><input class="form-control" id="payoff_onetime_frm_' + idx + '_amt" name="payoff_onetime_frm_' + idx + '_amt" type="text" size="8" style="width: auto;" onchange="setChanged()">';
s = s + '<span class="input-group-addon">' + CURRENCY_SYMBOL + '</span><input class="form-control" id="payoff_onetime_frm_' + idx + '_amt" name="payoff_onetime_frm_' + idx + '_amt" type="text" size="8" style="width: auto;" onchange="setChanged()">';
s = s + '</div> to the payment amount. (<a href="#" onclick="removeOnetime(' + idx + ')" id="rm_onetime_' + idx + '_link">remove</a>)</div></form>';
s = s + '<!-- /#payoff_onetime_frm_' + idx + ' -->';
$('#payoff_onetime_forms').append(s);
Expand Down
13 changes: 9 additions & 4 deletions biweeklybudget/flaskapp/static/js/custom.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,21 @@ function fmt_null(o) {
}

/**
* Format a float as currency
* Format a float as currency. If ``value`` is null, return ``&nbsp;``.
* Otherwise, construct a new instance of ``Intl.NumberFormat`` and use it to
* format the currency to a string. The formatter is called with the
* ``LOCALE_NAME`` and ``CURRENCY_CODE`` variables, which are templated into
* the header of ``base.html`` using the values specified in the Python
* settings module.
*
* @param {number} value - the number to format
* @returns {string} The number formatted as currency
*/
function fmt_currency(value) {
if (value === null) { return '&nbsp;'; }
return (
'$' + value.toFixed(2).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,")
).replace('$-', '-$');
return new Intl.NumberFormat(
LOCALE_NAME, { style: 'currency', currency: CURRENCY_CODE }
).format(value);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion biweeklybudget/flaskapp/static/js/formBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ FormBuilder.prototype.addCurrency = function(id, name, label, options) {
if (options.groupHtml !== null) { this.html += ' ' + options.groupHtml; }
this.html += '><label for="' + id + '" class="control-label">' + label + '</label>' +
'<div class="input-group">' +
'<span class="input-group-addon">$</span>' +
'<span class="input-group-addon">' + CURRENCY_SYMBOL + '</span>' +
'<input class="' + options.htmlClass + '" id="' + id + '" name="' + name + '" type="text">' +
'</div>';
if (options.helpBlock !== null) {
Expand Down
8 changes: 2 additions & 6 deletions biweeklybudget/flaskapp/static/js/fuel.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,17 +225,13 @@ $(document).ready(function() {
{
data: "cost_per_gallon",
"render": function(data, type, row) {
return type === "display" || type === "filter" ?
'$' + data.toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,") :
data;
return type === "display" || type === "filter" ? fmt_currency(data) : data;
}
},
{
data: "total_cost",
"render": function(data, type, row) {
return type === "display" || type === "filter" ?
'$' + data.toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,") :
data;
return type === "display" || type === "filter" ? fmt_currency(data) : data;
}
},
{
Expand Down
2 changes: 1 addition & 1 deletion biweeklybudget/flaskapp/static/js/fuel_charts.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ function initCharts() {
pointSize: 2,
hideHover: 'auto',
resize: true,
preUnits: '$',
preUnits: CURRENCY_SYMBOL,
continuousLine: true
});
});
Expand Down
2 changes: 1 addition & 1 deletion biweeklybudget/flaskapp/static/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ $(function() {
pointSize: 2,
hideHover: 'auto',
resize: true,
preUnits: '$',
preUnits: CURRENCY_SYMBOL,
continuousLine: true
});
});
Expand Down
11 changes: 11 additions & 0 deletions biweeklybudget/flaskapp/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@
<!-- App custom CSS -->
<link href="/static/css/custom.css" rel="stylesheet" type="text/css">

<!-- Python settings we need to be able to access from Javascript -->
<script>
var CURRENCY_SYMBOL = "{{ CURRENCY_SYM }}";
var LOCALE_NAME = "{{ settings.LOCALE_NAME }}";
if(LOCALE_NAME.indexOf('.') !== -1) {
LOCALE_NAME = LOCALE_NAME.substr(0, LOCALE_NAME.indexOf('.'));
}
if(LOCALE_NAME.indexOf('_') !== -1) { LOCALE_NAME = LOCALE_NAME.replace('_', '-'); }
var CURRENCY_CODE = "{{ settings.CURRENCY_CODE }}";
</script>

<!-- apply custom JS -->
<script src="/static/js/custom.js"></script>

Expand Down
2 changes: 1 addition & 1 deletion biweeklybudget/flaskapp/templates/credit-payoffs.html
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
<div class="form-group">
<label for="payoff_frm_min_pymt" class="control-label">Sum Of Minimum Monthly Payment(s)</label>
<div class="input-group">
<span class="input-group-addon">$</span><input class="form-control" id="payoff_frm_min_pymt" name="payoff_frm_min_pymt" type="text" size="8" style="width: auto;" value="{{ monthly_pymt_sum }}">
<span class="input-group-addon">{{ CURRENCY_SYM }}</span><input class="form-control" id="payoff_frm_min_pymt" name="payoff_frm_min_pymt" type="text" size="8" style="width: auto;" value="{{ monthly_pymt_sum }}">
</div> <!-- /.input-group -->
</div> <!-- /.form-group -->
</fieldset>
Expand Down
4 changes: 2 additions & 2 deletions biweeklybudget/flaskapp/templates/fuel.html
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
<th>Start Fuel Level</th>
<th>End Fuel Level</th>
<th>Location</th>
<th>$/Gal.</th>
<th>{{ CURRENCY_SYM }}/Gal.</th>
<th>Total Cost</th>
<th>Total Gallons</th>
<th>MPG (from veh.)</th>
Expand All @@ -94,7 +94,7 @@
<th>Start Fuel Level</th>
<th>End Fuel Level</th>
<th>Location</th>
<th>$/Gal.</th>
<th>{{ CURRENCY_SYM }}/Gal.</th>
<th>Total Cost</th>
<th>Total Gallons</th>
<th>MPG (from veh.)</th>
Expand Down
4 changes: 2 additions & 2 deletions biweeklybudget/flaskapp/views/credit_payoffs.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
import json
from decimal import Decimal, ROUND_UP
from datetime import datetime
from locale import currency

from flask.views import MethodView
from flask import render_template, request, jsonify
Expand All @@ -49,6 +48,7 @@
from biweeklybudget.db import db_session
from biweeklybudget.interest import InterestHelper
from biweeklybudget.models.dbsetting import DBSetting
from biweeklybudget.utils import fmt_currency

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -103,7 +103,7 @@ def _payoffs_list(self, ih):
tmp['results'].append({
'name': '%s (%d) (%s @ %s%%)' % (
acct.name, k,
currency(abs(acct.balance.ledger), grouping=True),
fmt_currency(abs(acct.balance.ledger)),
(acct.effective_apr * Decimal('100')).quantize(
Decimal('.01')
)
Expand Down
Loading

0 comments on commit 83541e0

Please sign in to comment.