mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-07-04 20:11:45 +02:00
Merge pull request #184
feat(tools:currency-converter): show 1:1 rates for all available currencies
This commit is contained in:
@@ -0,0 +1,85 @@
|
|||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from django.db.models import Func, F, Value
|
||||||
|
from django.db.models.functions import Extract
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from apps.currencies.models import ExchangeRate
|
||||||
|
|
||||||
|
|
||||||
|
def get_currency_exchange_map(date=None) -> Dict[str, dict]:
|
||||||
|
"""
|
||||||
|
Creates a nested dictionary of exchange rates and currency information.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
{
|
||||||
|
'BTC': {
|
||||||
|
'decimal_places': 8,
|
||||||
|
'prefix': '₿',
|
||||||
|
'suffix': '',
|
||||||
|
'rates': {'USD': Decimal('34000.00'), 'EUR': Decimal('31000.00')}
|
||||||
|
},
|
||||||
|
'USD': {
|
||||||
|
'decimal_places': 2,
|
||||||
|
'prefix': '$',
|
||||||
|
'suffix': '',
|
||||||
|
'rates': {'BTC': Decimal('0.0000294'), 'EUR': Decimal('0.91')}
|
||||||
|
},
|
||||||
|
...
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
if date is None:
|
||||||
|
date = timezone.localtime(timezone.now())
|
||||||
|
|
||||||
|
# Get all exchange rates for the closest date
|
||||||
|
exchange_rates = (
|
||||||
|
ExchangeRate.objects.select_related(
|
||||||
|
"from_currency", "to_currency"
|
||||||
|
) # Optimize currency queries
|
||||||
|
.annotate(
|
||||||
|
date_diff=Func(Extract(F("date") - Value(date), "epoch"), function="ABS"),
|
||||||
|
effective_rate=F("rate"),
|
||||||
|
)
|
||||||
|
.order_by("from_currency", "to_currency", "date_diff")
|
||||||
|
.distinct("from_currency", "to_currency")
|
||||||
|
)
|
||||||
|
|
||||||
|
# Initialize the result dictionary
|
||||||
|
rate_map = {}
|
||||||
|
|
||||||
|
# Build the exchange rate mapping with currency info
|
||||||
|
for rate in exchange_rates:
|
||||||
|
# Add from_currency info if not exists
|
||||||
|
if rate.from_currency.name not in rate_map:
|
||||||
|
rate_map[rate.from_currency.name] = {
|
||||||
|
"decimal_places": rate.from_currency.decimal_places,
|
||||||
|
"prefix": rate.from_currency.prefix,
|
||||||
|
"suffix": rate.from_currency.suffix,
|
||||||
|
"rates": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add to_currency info if not exists
|
||||||
|
if rate.to_currency.name not in rate_map:
|
||||||
|
rate_map[rate.to_currency.name] = {
|
||||||
|
"decimal_places": rate.to_currency.decimal_places,
|
||||||
|
"prefix": rate.to_currency.prefix,
|
||||||
|
"suffix": rate.to_currency.suffix,
|
||||||
|
"rates": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add direct rate
|
||||||
|
rate_map[rate.from_currency.name]["rates"][rate.to_currency.name] = {
|
||||||
|
"rate": rate.rate,
|
||||||
|
"decimal_places": rate.to_currency.decimal_places,
|
||||||
|
"prefix": rate.to_currency.prefix,
|
||||||
|
"suffix": rate.to_currency.suffix,
|
||||||
|
}
|
||||||
|
# Add inverse rate
|
||||||
|
rate_map[rate.to_currency.name]["rates"][rate.from_currency.name] = {
|
||||||
|
"rate": 1 / rate.rate,
|
||||||
|
"decimal_places": rate.from_currency.decimal_places,
|
||||||
|
"prefix": rate.from_currency.prefix,
|
||||||
|
"suffix": rate.from_currency.suffix,
|
||||||
|
}
|
||||||
|
|
||||||
|
return rate_map
|
||||||
@@ -5,6 +5,7 @@ from apps.common.widgets.decimal import convert_to_decimal
|
|||||||
from apps.currencies.models import Currency
|
from apps.currencies.models import Currency
|
||||||
from apps.currencies.utils.convert import convert
|
from apps.currencies.utils.convert import convert
|
||||||
from apps.mini_tools.forms import CurrencyConverterForm
|
from apps.mini_tools.forms import CurrencyConverterForm
|
||||||
|
from apps.mini_tools.utils.exchange_rate_map import get_currency_exchange_map
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@@ -14,11 +15,13 @@ def unit_price_calculator(request):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def currency_converter(request):
|
def currency_converter(request):
|
||||||
|
rate_map = get_currency_exchange_map()
|
||||||
|
|
||||||
form = CurrencyConverterForm()
|
form = CurrencyConverterForm()
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
"mini_tools/currency_converter/currency_converter.html",
|
"mini_tools/currency_converter/currency_converter.html",
|
||||||
context={"form": form},
|
context={"form": form, "rate_map": rate_map},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<div class="card tw-relative h-100 shadow">
|
<div class="card tw-relative h-100 shadow">
|
||||||
<div class="tw-absolute tw-h-8 tw-w-8 tw-right-2 tw-top-2 tw-bg-{{ color }}-300 tw-text-{{ color }}-800 text-center align-items-center d-flex justify-content-center rounded-2">
|
<div class="tw-absolute tw-h-8 tw-w-8 tw-right-2 tw-top-2 tw-bg-{{ color }}-300 tw-text-{{ color }}-800 text-center align-items-center d-flex justify-content-center rounded-2">
|
||||||
<i class="{{ icon }}"></i>
|
{% if icon %}<i class="{{ icon }}"></i>{% else %}<span class="fw-bold">{{ title.0 }}</span>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="tw-text-{{ color }}-400 fw-bold tw-mr-[50px]" {{ attrs }}>{{ title }}{% if help_text %}{% include 'includes/help_icon.html' with content=help_text %}{% endif %}</h5>
|
<h5 class="tw-text-{{ color }}-400 fw-bold tw-mr-[50px]" {{ attrs }}>{{ title }}{% if help_text %}{% include 'includes/help_icon.html' with content=help_text %}{% endif %}</h5>
|
||||||
{{ slot }}
|
{{ slot }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="tw-cursor-pointer text-primary text-end"
|
<div class="tw-cursor-pointer text-primary text-end"
|
||||||
_="on click
|
_="on click
|
||||||
set from_value to #id_from_currency's value
|
set from_value to #id_from_currency's value
|
||||||
set to_value to #id_to_currency's value
|
set to_value to #id_to_currency's value
|
||||||
@@ -58,5 +58,39 @@
|
|||||||
<i class="fa-solid fa-rotate me-2"></i><span>{% trans 'Invert' %}</span>
|
<i class="fa-solid fa-rotate me-2"></i><span>{% trans 'Invert' %}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
|
||||||
|
{% for currency, data in rate_map.items %}
|
||||||
|
<div class="col">
|
||||||
|
<c-ui.info-card color="yellow" title="{{ currency }}">
|
||||||
|
{% for rate in data.rates.values %}
|
||||||
|
<div class="d-flex justify-content-between align-items-baseline mt-2">
|
||||||
|
<div class="text-end font-monospace">
|
||||||
|
<div class="tw-text-gray-400">
|
||||||
|
{# <c-amount.display#}
|
||||||
|
{# :amount="1"#}
|
||||||
|
{# :prefix="data.prefix"#}
|
||||||
|
{# :suffix="data.suffix"#}
|
||||||
|
{# :decimal_places="data.decimal_places"></c-amount.display>#}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dotted-line flex-grow-1"></div>
|
||||||
|
{% if currency.income_projected != 0 %}
|
||||||
|
<div class="text-end font-monospace tw-text-green-400">
|
||||||
|
<c-amount.display
|
||||||
|
:amount="rate.rate"
|
||||||
|
:prefix="rate.prefix"
|
||||||
|
:suffix="rate.suffix"
|
||||||
|
:decimal_places="rate.decimal_places"></c-amount.display>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="text-end font-monospace">-</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</c-ui.info-card>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user