This commit is contained in:
Herculino Trotta
2024-10-09 00:31:21 -03:00
parent e78e4cc5e1
commit 3dde44b1cd
139 changed files with 4965 additions and 1004 deletions
+4 -1
View File
@@ -1,6 +1,6 @@
from django.contrib import admin
from apps.currencies.models import Currency
from apps.currencies.models import Currency, ExchangeRate
@admin.register(Currency)
@@ -9,3 +9,6 @@ class CurrencyAdmin(admin.ModelAdmin):
if db_field.name == "suffix" or db_field.name == "prefix":
kwargs["strip"] = False
return super().formfield_for_dbfield(db_field, request, **kwargs)
admin.site.register(ExchangeRate)
+45
View File
@@ -0,0 +1,45 @@
from crispy_forms.bootstrap import FormActions
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout
from django import forms
from django.forms import CharField
from django.utils.translation import gettext_lazy as _
from apps.common.widgets.crispy.submit import NoClassSubmit
from apps.currencies.models import Currency
class CurrencyForm(forms.ModelForm):
prefix = CharField(strip=False, required=False)
suffix = CharField(strip=False, required=False)
class Meta:
model = Currency
fields = ["name", "decimal_places", "prefix", "suffix", "code"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.form_method = "post"
self.helper.layout = Layout(
"code", "name", "decimal_places", "prefix", "suffix"
)
if self.instance and self.instance.pk:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
),
)
else:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
),
)
@@ -0,0 +1,58 @@
# Generated by Django 5.1.1 on 2024-10-04 03:33
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("currencies", "0003_alter_currency_decimal_places"),
]
operations = [
migrations.CreateModel(
name="ExchangeRate",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"rate",
models.DecimalField(
decimal_places=30, max_digits=42, verbose_name="Exchange Rate"
),
),
("date", models.DateTimeField(verbose_name="Date and Time")),
(
"from_currency",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="from_exchange_rates",
to="currencies.currency",
verbose_name="From Currency",
),
),
(
"to_currency",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="to_exchange_rates",
to="currencies.currency",
verbose_name="To Currency",
),
),
],
options={
"verbose_name": "Exchange Rate",
"verbose_name_plural": "Exchange Rates",
"unique_together": {("from_currency", "to_currency", "date")},
},
),
]
+27
View File
@@ -20,3 +20,30 @@ class Currency(models.Model):
class Meta:
verbose_name = _("Currency")
verbose_name_plural = _("Currencies")
class ExchangeRate(models.Model):
from_currency = models.ForeignKey(
Currency,
on_delete=models.CASCADE,
related_name="from_exchange_rates",
verbose_name=_("From Currency"),
)
to_currency = models.ForeignKey(
Currency,
on_delete=models.CASCADE,
related_name="to_exchange_rates",
verbose_name=_("To Currency"),
)
rate = models.DecimalField(
max_digits=42, decimal_places=30, verbose_name=_("Exchange Rate")
)
date = models.DateTimeField(verbose_name=_("Date and Time"))
class Meta:
verbose_name = _("Exchange Rate")
verbose_name_plural = _("Exchange Rates")
unique_together = ("from_currency", "to_currency", "date")
def __str__(self):
return f"{self.from_currency.code} to {self.to_currency.code} on {self.date}"
+18
View File
@@ -0,0 +1,18 @@
from django.urls import path
from . import views
urlpatterns = [
path("currencies/", views.currency_list, name="currencies_list"),
path("currencies/add/", views.currency_add, name="currency_add"),
path(
"currencies/<int:pk>/edit/",
views.currency_edit,
name="currency_edit",
),
path(
"currencies/<int:pk>/delete/",
views.currency_delete,
name="currency_delete",
),
]
+42
View File
@@ -0,0 +1,42 @@
from django.db.models import Func, F, Value, DurationField, Case, When, DecimalField, Q
from django.db.models.functions import Abs, Extract
from django.utils import timezone
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from apps.currencies.models import ExchangeRate
def convert(amount, from_currency, to_currency, date=None):
if from_currency == to_currency:
return amount
if date is None:
date = timezone.localtime(timezone.now())
try:
exchange_rate = (
ExchangeRate.objects.filter(
Q(from_currency=from_currency, to_currency=to_currency)
| Q(from_currency=to_currency, to_currency=from_currency)
)
.annotate(
date_diff=Func(
Extract(F("date") - Value(date), "epoch"), function="ABS"
),
effective_rate=Case(
When(from_currency=from_currency, then=F("rate")),
default=1 / F("rate"),
output_field=DecimalField(),
),
)
.order_by("date_diff")
.first()
)
if exchange_rate is None:
return None
return amount * exchange_rate.effective_rate
except ExchangeRate.DoesNotExist:
return None
+91 -2
View File
@@ -1,3 +1,92 @@
from django.shortcuts import render
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.shortcuts import render, get_object_or_404
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
# Create your views here.
from apps.common.decorators.htmx import only_htmx
from apps.currencies.forms import CurrencyForm
from apps.currencies.models import Currency
@login_required
@require_http_methods(["GET"])
def currency_list(request):
currencies = Currency.objects.all().order_by("id")
return render(request, "currencies/pages/list.html", {"currencies": currencies})
@only_htmx
@login_required
@require_http_methods(["GET", "POST"])
def currency_add(request, **kwargs):
if request.method == "POST":
form = CurrencyForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, _("Currency added successfully"))
return HttpResponse(
status=204,
headers={
"HX-Location": reverse("currencies_list"),
"HX-Trigger": "hide_offcanvas, toasts",
},
)
else:
form = CurrencyForm()
return render(
request,
"currencies/fragments/add.html",
{"form": form},
)
@only_htmx
@login_required
@require_http_methods(["GET", "POST"])
def currency_edit(request, pk):
currency = get_object_or_404(Currency, id=pk)
if request.method == "POST":
form = CurrencyForm(request.POST, instance=currency)
if form.is_valid():
form.save()
messages.success(request, _("Currency updated successfully"))
return HttpResponse(
status=204,
headers={
"HX-Location": reverse("currencies_list"),
"HX-Trigger": "hide_offcanvas, toasts",
},
)
else:
form = CurrencyForm(instance=currency)
return render(
request,
"currencies/fragments/edit.html",
{"form": form, "currency": currency},
)
@only_htmx
@login_required
@csrf_exempt
@require_http_methods(["DELETE"])
def currency_delete(request, pk):
currency = get_object_or_404(Currency, id=pk)
currency.delete()
messages.success(request, _("Currency deleted successfully"))
return HttpResponse(
status=204,
headers={"HX-Location": reverse("currencies_list")},
)