aboutsummaryrefslogtreecommitdiff
path: root/ihatemoney
diff options
context:
space:
mode:
Diffstat (limited to 'ihatemoney')
-rw-r--r--ihatemoney/currency_convertor.py18
-rw-r--r--ihatemoney/forms.py75
-rw-r--r--ihatemoney/migrations/versions/927ed575acbd_add_currencies.py4
-rw-r--r--ihatemoney/run.py18
-rw-r--r--ihatemoney/templates/forms.html2
-rw-r--r--ihatemoney/templates/list_bills.html19
-rw-r--r--ihatemoney/utils.py17
-rw-r--r--ihatemoney/web.py11
8 files changed, 92 insertions, 72 deletions
diff --git a/ihatemoney/currency_convertor.py b/ihatemoney/currency_convertor.py
index 75fa834..10026ee 100644
--- a/ihatemoney/currency_convertor.py
+++ b/ihatemoney/currency_convertor.py
@@ -13,7 +13,7 @@ class Singleton(type):
class CurrencyConverter(object, metaclass=Singleton):
# Get exchange rates
- default = "No Currency"
+ no_currency = "XXX"
api_url = "https://api.exchangeratesapi.io/latest?base=USD"
def __init__(self):
@@ -22,19 +22,23 @@ class CurrencyConverter(object, metaclass=Singleton):
@cached(cache=TTLCache(maxsize=1, ttl=86400))
def get_rates(self):
rates = requests.get(self.api_url).json()["rates"]
- rates[self.default] = 1.0
+ rates[self.no_currency] = 1.0
return rates
- def get_currencies(self):
- rates = [rate for rate in self.get_rates()]
- rates.sort(key=lambda rate: "" if rate == self.default else rate)
+ def get_currencies(self, with_no_currency=True):
+ rates = [
+ rate
+ for rate in self.get_rates()
+ if with_no_currency or rate != self.no_currency
+ ]
+ rates.sort(key=lambda rate: "" if rate == self.no_currency else rate)
return rates
def exchange_currency(self, amount, source_currency, dest_currency):
if (
source_currency == dest_currency
- or source_currency == self.default
- or dest_currency == self.default
+ or source_currency == self.no_currency
+ or dest_currency == self.no_currency
):
return amount
diff --git a/ihatemoney/forms.py b/ihatemoney/forms.py
index 7a6a57e..8fd13f6 100644
--- a/ihatemoney/forms.py
+++ b/ihatemoney/forms.py
@@ -1,4 +1,3 @@
-import copy
from datetime import datetime
from re import match
@@ -23,7 +22,11 @@ from wtforms.validators import (
from ihatemoney.currency_convertor import CurrencyConverter
from ihatemoney.models import LoggingMode, Person, Project
-from ihatemoney.utils import eval_arithmetic_expression, slugify
+from ihatemoney.utils import (
+ eval_arithmetic_expression,
+ render_localized_currency,
+ slugify,
+)
def strip_filter(string):
@@ -33,18 +36,6 @@ def strip_filter(string):
return string
-def get_editprojectform_for(project, **kwargs):
- """Return an instance of EditProjectForm configured for a particular project.
- """
- form = EditProjectForm(**kwargs)
- choices = copy.copy(form.default_currency.choices)
- choices.sort(
- key=lambda rates: "" if rates[0] == project.default_currency else rates[0]
- )
- form.default_currency.choices = choices
- return form
-
-
def get_billform_for(project, set_default=True, **kwargs):
"""Return an instance of BillForm configured for a particular project.
@@ -56,20 +47,15 @@ def get_billform_for(project, set_default=True, **kwargs):
if form.original_currency.data == "None":
form.original_currency.data = project.default_currency
- if form.original_currency.data != CurrencyConverter.default:
- choices = copy.copy(form.original_currency.choices)
- choices.remove((CurrencyConverter.default, CurrencyConverter.default))
- choices.sort(
- key=lambda rates: "" if rates[0] == project.default_currency else rates[0]
+ show_no_currency = form.original_currency.data == CurrencyConverter.no_currency
+
+ form.original_currency.choices = [
+ (currency_name, render_localized_currency(currency_name, detailed=False))
+ for currency_name in form.currency_helper.get_currencies(
+ with_no_currency=show_no_currency
)
- form.original_currency.choices = choices
- else:
- form.original_currency.render_kw = {"default": True}
- form.original_currency.data = CurrencyConverter.default
+ ]
- form.original_currency.label = Label(
- "original_currency", "Currency (Default: %s)" % (project.default_currency)
- )
active_members = [(m.id, m.name) for m in project.active_members]
form.payed_for.choices = form.payer.choices = active_members
@@ -121,14 +107,14 @@ class EditProjectForm(FlaskForm):
project_history = BooleanField(_("Enable project history"))
ip_recording = BooleanField(_("Use IP tracking for project history"))
currency_helper = CurrencyConverter()
- default_currency = SelectField(
- _("Default Currency"),
- choices=[
- (currency_name, currency_name)
- for currency_name in currency_helper.get_currencies()
- ],
- validators=[DataRequired()],
- )
+ default_currency = SelectField(_("Default Currency"), validators=[DataRequired()],)
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.default_currency.choices = [
+ (currency_name, render_localized_currency(currency_name, detailed=True))
+ for currency_name in self.currency_helper.get_currencies()
+ ]
@property
def logging_preference(self):
@@ -242,14 +228,7 @@ class BillForm(FlaskForm):
payer = SelectField(_("Payer"), validators=[DataRequired()], coerce=int)
amount = CalculatorStringField(_("Amount paid"), validators=[DataRequired()])
currency_helper = CurrencyConverter()
- original_currency = SelectField(
- _("Currency"),
- choices=[
- (currency_name, currency_name)
- for currency_name in currency_helper.get_currencies()
- ],
- validators=[DataRequired()],
- )
+ original_currency = SelectField(_("Currency"), validators=[DataRequired()],)
external_link = URLField(
_("External link"),
validators=[Optional()],
@@ -281,14 +260,14 @@ class BillForm(FlaskForm):
bill.external_link = ""
bill.date = self.date
bill.owers = [Person.query.get(ower, project) for ower in self.payed_for]
- bill.original_currency = CurrencyConverter.default
+ bill.original_currency = CurrencyConverter.no_currency
bill.converted_amount = self.currency_helper.exchange_currency(
bill.amount, bill.original_currency, project.default_currency
)
return bill
- def fill(self, bill):
+ def fill(self, bill, project):
self.payer.data = bill.payer_id
self.amount.data = bill.amount
self.what.data = bill.what
@@ -297,6 +276,14 @@ class BillForm(FlaskForm):
self.date.data = bill.date
self.payed_for.data = [int(ower.id) for ower in bill.owers]
+ self.original_currency.label = Label("original_currency", _("Currency"))
+ self.original_currency.description = _(
+ "Project default: %(currency)s",
+ currency=render_localized_currency(
+ project.default_currency, detailed=False
+ ),
+ )
+
def set_default(self):
self.payed_for.data = self.payed_for.default
diff --git a/ihatemoney/migrations/versions/927ed575acbd_add_currencies.py b/ihatemoney/migrations/versions/927ed575acbd_add_currencies.py
index b70d902..88b8a5b 100644
--- a/ihatemoney/migrations/versions/927ed575acbd_add_currencies.py
+++ b/ihatemoney/migrations/versions/927ed575acbd_add_currencies.py
@@ -23,7 +23,7 @@ def upgrade():
sa.Column(
"original_currency",
sa.String(length=3),
- server_default=CurrencyConverter.default,
+ server_default=CurrencyConverter.no_currency,
nullable=True,
),
)
@@ -42,7 +42,7 @@ def upgrade():
sa.Column(
"default_currency",
sa.String(length=3),
- server_default=CurrencyConverter.default,
+ server_default=CurrencyConverter.no_currency,
nullable=True,
),
)
diff --git a/ihatemoney/run.py b/ihatemoney/run.py
index b6c8cbb..e084e5b 100644
--- a/ihatemoney/run.py
+++ b/ihatemoney/run.py
@@ -3,7 +3,7 @@ import os.path
import warnings
from flask import Flask, g, render_template, request, session
-from flask_babel import Babel
+from flask_babel import Babel, format_currency
from flask_mail import Mail
from flask_migrate import Migrate, stamp, upgrade
from werkzeug.middleware.proxy_fix import ProxyFix
@@ -153,6 +153,22 @@ def create_app(
# Translations
babel = Babel(app)
+ # Undocumented currencyformat filter from flask_babel is forwarding to Babel format_currency
+ # We overwrite it to remove the currency sign ¤ when there is no currency
+ def currencyformat_nc(number, currency, *args, **kwargs):
+ """
+ Same as flask_babel.Babel.currencyformat, but without the "no currency ¤" sign
+ when there is no currency.
+ """
+ return format_currency(
+ number,
+ currency if currency != CurrencyConverter.no_currency else "",
+ *args,
+ **kwargs
+ ).strip()
+
+ app.jinja_env.filters["currencyformat_nc"] = currencyformat_nc
+
@babel.localeselector
def get_locale():
# get the lang from the session if defined, fallback on the browser "accept
diff --git a/ihatemoney/templates/forms.html b/ihatemoney/templates/forms.html
index 0900d2f..82b960e 100644
--- a/ihatemoney/templates/forms.html
+++ b/ihatemoney/templates/forms.html
@@ -124,7 +124,7 @@
{{ input(form.what, inline=True) }}
{{ input(form.payer, inline=True, class="form-control custom-select") }}
{{ input(form.amount, inline=True) }}
- {% if not form.original_currency.render_kw %}
+ {% if g.project.default_currency != "XXX" %}
{{ input(form.original_currency, inline=True) }}
{% endif %}
{{ input(form.external_link, inline=True) }}
diff --git a/ihatemoney/templates/list_bills.html b/ihatemoney/templates/list_bills.html
index be55c19..7ae3bd6 100644
--- a/ihatemoney/templates/list_bills.html
+++ b/ihatemoney/templates/list_bills.html
@@ -1,5 +1,9 @@
{% extends "sidebar_table_layout.html" %}
+{%- macro bill_amount(bill, currency=bill.original_currency, amount=bill.amount) %}
+ {{ amount|currencyformat_nc(currency) }} ({{ _("%(amount)s each", amount=bill.pay_each_default(amount)|currencyformat_nc(currency)) }})
+{% endmacro -%}
+
{% block title %} - {{ g.project.name }}{% endblock %}
{% block js %}
{% if add_bill %} $('#new-bill > a').click(); {% endif %}
@@ -123,11 +127,6 @@
</th><th>{{ _("For what?") }}
</th><th>{{ _("For whom?") }}
</th><th>{{ _("How much?") }}
- {% if g.project.default_currency != "No Currency" %}
- </th><th>{{ _("Amount in %(currency)s", currency=g.project.default_currency) }}
- {%- else -%}
- </th><th>{{ _("Amount") }}
- {% endif %}
</th><th>{{ _("Actions") }}</th></tr>
</thead>
<tbody>
@@ -149,13 +148,11 @@
{{ bill.owers|join(', ', 'name') }}
{%- endif %}</td>
<td>
- {% if bill.original_currency != "No Currency" %}
- {{ "%0.2f"|format(bill.amount) }} {{bill.original_currency}} ({{ "%0.2f"|format(bill.pay_each_default(bill.amount)) }} {{bill.original_currency}} {{ _(" each") }})
- {%- else -%}
- {{ "%0.2f"|format(bill.amount) }} ({{ "%0.2f"|format(bill.pay_each_default(bill.amount)) }} {{ _(" each") }})
- {% endif %}
+ <span data-toggle="tooltip" data-placement="top"
+ title="{{ bill_amount(bill, g.project.default_currency, bill.converted_amount) if bill.original_currency != g.project.default_currency else '' }}">
+ {{ bill_amount(bill) }}
+ </span>
</td>
- <td>{{ "%0.2f"|format(bill.converted_amount) }}</td>
<td class="bill-actions">
<a class="edit" href="{{ url_for(".edit_bill", bill_id=bill.id) }}" title="{{ _("edit") }}">{{ _('edit') }}</a>
<a class="delete" href="{{ url_for(".delete_bill", bill_id=bill.id) }}" title="{{ _("delete") }}">{{ _('delete') }}</a>
diff --git a/ihatemoney/utils.py b/ihatemoney/utils.py
index 7fdad61..175b762 100644
--- a/ihatemoney/utils.py
+++ b/ihatemoney/utils.py
@@ -9,8 +9,9 @@ import os
import re
from babel import Locale
+from babel.numbers import get_currency_name, get_currency_symbol
from flask import current_app, redirect, render_template
-from flask_babel import get_locale
+from flask_babel import get_locale, lazy_gettext as _
import jinja2
from werkzeug.routing import HTTPException, RoutingException
@@ -281,6 +282,20 @@ class FormEnum(Enum):
return str(self.value)
+def render_localized_currency(code, detailed=True):
+ if code == "XXX":
+ return _("No Currency")
+ locale = get_locale() or "en_US"
+ symbol = get_currency_symbol(code, locale=locale)
+ details = ""
+ if detailed:
+ details = f" − {get_currency_name(code, locale=locale)}"
+ if symbol == code:
+ return f"{code}{details}"
+ else:
+ return f"{code} − {symbol}{details}"
+
+
def render_localized_template(template_name_prefix, **context):
"""Like render_template(), but selects the right template according to the
current user language. Fallback to English if a template for the
diff --git a/ihatemoney/web.py b/ihatemoney/web.py
index bbc98c4..ae124ac 100644
--- a/ihatemoney/web.py
+++ b/ihatemoney/web.py
@@ -41,6 +41,7 @@ from ihatemoney.currency_convertor import CurrencyConverter
from ihatemoney.forms import (
AdminAuthenticationForm,
AuthenticationForm,
+ EditProjectForm,
InviteForm,
MemberForm,
PasswordReminder,
@@ -48,7 +49,6 @@ from ihatemoney.forms import (
ResetPasswordForm,
UploadForm,
get_billform_for,
- get_editprojectform_for,
)
from ihatemoney.history import get_history, get_history_queries
from ihatemoney.models import Bill, LoggingMode, Person, Project, db
@@ -377,7 +377,7 @@ def reset_password():
@main.route("/<project_id>/edit", methods=["GET", "POST"])
def edit_project():
- edit_form = get_editprojectform_for(g.project)
+ edit_form = EditProjectForm()
import_form = UploadForm()
# Import form
if import_form.validate_on_submit():
@@ -393,10 +393,10 @@ def edit_project():
if edit_form.validate_on_submit():
project = edit_form.update(g.project)
# Update converted currency
- if project.default_currency != CurrencyConverter.default:
+ if project.default_currency != CurrencyConverter.no_currency:
for bill in project.get_bills():
- if bill.original_currency == CurrencyConverter.default:
+ if bill.original_currency == CurrencyConverter.no_currency:
bill.original_currency = project.default_currency
bill.converted_amount = CurrencyConverter().exchange_currency(
@@ -417,6 +417,7 @@ def edit_project():
edit_form.ip_recording.data = True
edit_form.contact_email.data = g.project.contact_email
+ edit_form.default_currency.data = g.project.default_currency
return render_template(
"edit_project.html",
@@ -732,7 +733,7 @@ def edit_bill(bill_id):
return redirect(url_for(".list_bills"))
if not form.errors:
- form.fill(bill)
+ form.fill(bill, g.project)
return render_template("add_bill.html", form=form, edit=True)