mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-04-19 07:19:53 +02:00
feat: finish adding DCA Tracker tool
This commit is contained in:
@@ -1,16 +1,24 @@
|
||||
from crispy_forms.bootstrap import FormActions
|
||||
from django import forms
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Layout, Row, Column
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .models import DCAStrategy, DCAEntry
|
||||
from apps.common.widgets.tom_select import TomSelect
|
||||
from apps.dca.models import DCAStrategy, DCAEntry
|
||||
from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput
|
||||
from apps.common.widgets.crispy.submit import NoClassSubmit
|
||||
|
||||
|
||||
class DCAStrategyForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = DCAStrategy
|
||||
fields = ["name", "target_currency", "payment_currency", "notes"]
|
||||
widgets = {
|
||||
"target_currency": TomSelect(clear_button=False),
|
||||
"payment_currency": TomSelect(clear_button=False),
|
||||
"notes": forms.Textarea(attrs={"rows": 3}),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -19,12 +27,29 @@ class DCAStrategyForm(forms.ModelForm):
|
||||
self.helper.layout = Layout(
|
||||
"name",
|
||||
Row(
|
||||
Column("target_currency", css_class="form-group col-md-6"),
|
||||
Column("payment_currency", css_class="form-group col-md-6"),
|
||||
Column("target_currency", css_class="form-group col-md-6"),
|
||||
),
|
||||
"notes",
|
||||
)
|
||||
|
||||
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"
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class DCAEntryForm(forms.ModelForm):
|
||||
class Meta:
|
||||
@@ -33,13 +58,11 @@ class DCAEntryForm(forms.ModelForm):
|
||||
"date",
|
||||
"amount_paid",
|
||||
"amount_received",
|
||||
"expense_transaction",
|
||||
"income_transaction",
|
||||
"notes",
|
||||
]
|
||||
widgets = {
|
||||
"amount_paid": ArbitraryDecimalDisplayNumberInput(decimal_places=8),
|
||||
"amount_received": ArbitraryDecimalDisplayNumberInput(decimal_places=8),
|
||||
"date": forms.DateInput(attrs={"type": "date"}, format="%Y-%m-%d"),
|
||||
"notes": forms.Textarea(attrs={"rows": 3}),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -58,3 +81,28 @@ class DCAEntryForm(forms.ModelForm):
|
||||
),
|
||||
"notes",
|
||||
)
|
||||
|
||||
if self.instance and self.instance.pk:
|
||||
# decimal_places = self.instance.account.currency.decimal_places
|
||||
# self.fields["amount"].widget = ArbitraryDecimalDisplayNumberInput(
|
||||
# decimal_places=decimal_places
|
||||
# )
|
||||
self.helper.layout.append(
|
||||
FormActions(
|
||||
NoClassSubmit(
|
||||
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
|
||||
),
|
||||
),
|
||||
)
|
||||
else:
|
||||
# self.fields["amount"].widget = ArbitraryDecimalDisplayNumberInput()
|
||||
self.helper.layout.append(
|
||||
FormActions(
|
||||
NoClassSubmit(
|
||||
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
self.fields["amount_paid"].widget = ArbitraryDecimalDisplayNumberInput()
|
||||
self.fields["amount_received"].widget = ArbitraryDecimalDisplayNumberInput()
|
||||
|
||||
@@ -3,7 +3,34 @@ from . import views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path("dca/", views.strategy_list, name="strategy_list"),
|
||||
path("dca/<int:pk>/", views.strategy_detail, name="strategy_detail"),
|
||||
# Add more URLs for CRUD operations
|
||||
path("dca/", views.strategy_index, name="dca_strategy_index"),
|
||||
path("dca/list/", views.strategy_list, name="dca_strategy_list"),
|
||||
path("dca/add/", views.strategy_add, name="dca_strategy_add"),
|
||||
path("dca/<int:strategy_id>/edit/", views.strategy_edit, name="dca_strategy_edit"),
|
||||
path(
|
||||
"dca/<int:strategy_id>/delete/",
|
||||
views.strategy_delete,
|
||||
name="dca_strategy_delete",
|
||||
),
|
||||
path(
|
||||
"dca/<int:strategy_id>/",
|
||||
views.strategy_detail_index,
|
||||
name="dca_strategy_detail_index",
|
||||
),
|
||||
path(
|
||||
"dca/<int:strategy_id>/details/",
|
||||
views.strategy_detail,
|
||||
name="dca_strategy_detail",
|
||||
),
|
||||
path("dca/<int:strategy_id>/add/", views.strategy_entry_add, name="dca_entry_add"),
|
||||
path(
|
||||
"dca/<int:strategy_id>/<int:entry_id>/edit/",
|
||||
views.strategy_entry_edit,
|
||||
name="dca_entry_edit",
|
||||
),
|
||||
path(
|
||||
"dca/<int:strategy_id>/<int:entry_id>/delete/",
|
||||
views.strategy_entry_delete,
|
||||
name="dca_entry_delete",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,31 +1,119 @@
|
||||
# apps/dca_tracker/views.py
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db.models import Sum, Avg
|
||||
from django.db.models.functions import TruncMonth
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
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
|
||||
|
||||
from .models import DCAStrategy, DCAEntry
|
||||
from .forms import DCAStrategyForm, DCAEntryForm
|
||||
from apps.common.decorators.htmx import only_htmx
|
||||
from apps.dca.models import DCAStrategy, DCAEntry
|
||||
from apps.dca.forms import DCAEntryForm, DCAStrategyForm
|
||||
|
||||
|
||||
@login_required
|
||||
def strategy_index(request):
|
||||
return render(request, "dca/pages/strategy_index.html")
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
def strategy_list(request):
|
||||
strategies = DCAStrategy.objects.all()
|
||||
return render(request, "dca/strategy_list.html", {"strategies": strategies})
|
||||
strategies = DCAStrategy.objects.all().order_by("created_at")
|
||||
return render(
|
||||
request, "dca/fragments/strategy/list.html", {"strategies": strategies}
|
||||
)
|
||||
|
||||
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.db.models import Sum, Avg
|
||||
from django.db.models.functions import TruncMonth
|
||||
@only_htmx
|
||||
@login_required
|
||||
def strategy_add(request):
|
||||
if request.method == "POST":
|
||||
form = DCAStrategyForm(request.POST)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, _("DCA Strategy added successfully"))
|
||||
|
||||
return HttpResponse(
|
||||
status=204,
|
||||
headers={
|
||||
"HX-Trigger": "updated, hide_offcanvas, toasts",
|
||||
},
|
||||
)
|
||||
else:
|
||||
form = DCAStrategyForm()
|
||||
|
||||
return render(
|
||||
request,
|
||||
"dca/fragments/strategy/add.html",
|
||||
{"form": form},
|
||||
)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
def strategy_edit(request, strategy_id):
|
||||
dca_strategy = get_object_or_404(DCAStrategy, id=strategy_id)
|
||||
|
||||
if request.method == "POST":
|
||||
form = DCAStrategyForm(request.POST, instance=dca_strategy)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, _("DCA Strategy updated successfully"))
|
||||
|
||||
return HttpResponse(
|
||||
status=204,
|
||||
headers={
|
||||
"HX-Trigger": "updated, hide_offcanvas, toasts",
|
||||
},
|
||||
)
|
||||
else:
|
||||
form = DCAStrategyForm(instance=dca_strategy)
|
||||
|
||||
return render(
|
||||
request,
|
||||
"dca/fragments/strategy/edit.html",
|
||||
{"form": form, "dca_strategy": dca_strategy},
|
||||
)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
@csrf_exempt
|
||||
@require_http_methods(["DELETE"])
|
||||
def strategy_delete(request, strategy_id):
|
||||
dca_strategy = get_object_or_404(DCAStrategy, id=strategy_id)
|
||||
|
||||
dca_strategy.delete()
|
||||
|
||||
messages.success(request, _("DCA strategy deleted successfully"))
|
||||
|
||||
return HttpResponse(
|
||||
status=204,
|
||||
headers={
|
||||
"HX-Trigger": "updated, hide_offcanvas, toasts",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
def strategy_detail(request, pk):
|
||||
strategy = get_object_or_404(DCAStrategy, id=pk)
|
||||
def strategy_detail_index(request, strategy_id):
|
||||
strategy = get_object_or_404(DCAStrategy, id=strategy_id)
|
||||
|
||||
return render(
|
||||
request,
|
||||
"dca/pages/strategy_detail_index.html",
|
||||
context={"strategy": strategy},
|
||||
)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
def strategy_detail(request, strategy_id):
|
||||
strategy = get_object_or_404(DCAStrategy, id=strategy_id)
|
||||
entries = strategy.entries.all()
|
||||
|
||||
# Calculate monthly aggregates
|
||||
@@ -50,6 +138,7 @@ def strategy_detail(request, pk):
|
||||
}
|
||||
for entry in entries
|
||||
]
|
||||
entries_data.reverse()
|
||||
|
||||
context = {
|
||||
"strategy": strategy,
|
||||
@@ -64,4 +153,78 @@ def strategy_detail(request, pk):
|
||||
"total_profit_loss": strategy.total_profit_loss(),
|
||||
"total_profit_loss_percentage": strategy.total_profit_loss_percentage(),
|
||||
}
|
||||
return render(request, "dca/strategy_detail.html", context)
|
||||
return render(request, "dca/fragments/strategy/details.html", context)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
def strategy_entry_add(request, strategy_id):
|
||||
strategy = get_object_or_404(DCAStrategy, id=strategy_id)
|
||||
if request.method == "POST":
|
||||
form = DCAEntryForm(request.POST)
|
||||
if form.is_valid():
|
||||
entry = form.save(commit=False)
|
||||
entry.strategy = strategy
|
||||
entry.save()
|
||||
messages.success(request, _("Entry added successfully"))
|
||||
|
||||
return HttpResponse(
|
||||
status=204,
|
||||
headers={
|
||||
"HX-Trigger": "updated, hide_offcanvas, toasts",
|
||||
},
|
||||
)
|
||||
else:
|
||||
form = DCAEntryForm()
|
||||
|
||||
return render(
|
||||
request,
|
||||
"dca/fragments/entry/add.html",
|
||||
{"form": form, "strategy": strategy},
|
||||
)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
def strategy_entry_edit(request, strategy_id, entry_id):
|
||||
dca_entry = get_object_or_404(DCAEntry, id=entry_id, strategy__id=strategy_id)
|
||||
|
||||
if request.method == "POST":
|
||||
form = DCAEntryForm(request.POST, instance=dca_entry)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, _("Entry updated successfully"))
|
||||
|
||||
return HttpResponse(
|
||||
status=204,
|
||||
headers={
|
||||
"HX-Trigger": "updated, hide_offcanvas, toasts",
|
||||
},
|
||||
)
|
||||
else:
|
||||
form = DCAEntryForm(instance=dca_entry)
|
||||
|
||||
return render(
|
||||
request,
|
||||
"dca/fragments/strategy/edit.html",
|
||||
{"form": form, "dca_entry": dca_entry},
|
||||
)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
@csrf_exempt
|
||||
@require_http_methods(["DELETE"])
|
||||
def strategy_entry_delete(request, entry_id, strategy_id):
|
||||
dca_entry = get_object_or_404(DCAEntry, id=entry_id, strategy__id=strategy_id)
|
||||
|
||||
dca_entry.delete()
|
||||
|
||||
messages.success(request, _("Entry deleted successfully"))
|
||||
|
||||
return HttpResponse(
|
||||
status=204,
|
||||
headers={
|
||||
"HX-Trigger": "updated, hide_offcanvas, toasts",
|
||||
},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user