From 9b381a0e180246e442b714a17e40513339cef386 Mon Sep 17 00:00:00 2001 From: Gabriel Augendre Date: Mon, 27 Mar 2023 17:35:16 +0200 Subject: [PATCH] Display total amount preview. Close #2 --- src/purchase/forms.py | 8 +++++++ .../static/purchase/js/basket_form.js | 19 +++++++++++---- src/purchase/urls.py | 11 +++++++-- src/purchase/views/__init__.py | 18 +++++++++++++-- src/purchase/views/basket.py | 23 +++++++++++++++---- 5 files changed, 67 insertions(+), 12 deletions(-) diff --git a/src/purchase/forms.py b/src/purchase/forms.py index 8e73fb8..4c6ee72 100644 --- a/src/purchase/forms.py +++ b/src/purchase/forms.py @@ -2,6 +2,7 @@ from crispy_forms.bootstrap import InlineRadios from crispy_forms.helper import FormHelper from crispy_forms.layout import Div, Layout, Submit from django import forms +from django.urls import reverse from django.utils.translation import gettext as _ from purchase.layout import BasketItemField @@ -25,6 +26,12 @@ class BasketForm(forms.ModelForm): self.helper = FormHelper() self.helper.form_class = "form-horizontal" self.helper.add_input(Submit("submit", _("Save"))) + self.helper.attrs = { + "hx_post": reverse("purchase:price_preview"), + "hx_trigger": "change", + "hx_target": "#price_preview", + "hx_swap": "innerHTML", + } self.helper.layout = Layout() products = {} basket = kwargs.get("instance") @@ -51,6 +58,7 @@ class BasketForm(forms.ModelForm): css_id="products", ), InlineRadios("payment_method"), + Div(css_id="price_preview", css_class="mb-2"), ) def save(self): diff --git a/src/purchase/static/purchase/js/basket_form.js b/src/purchase/static/purchase/js/basket_form.js index 9d7437f..02890c4 100644 --- a/src/purchase/static/purchase/js/basket_form.js +++ b/src/purchase/static/purchase/js/basket_form.js @@ -1,14 +1,25 @@ window.incrementValue = function (id) { - let value = parseInt(document.getElementById(id).value); + const element = document.getElementById(id); + let value = parseInt(element.value); value = isNaN(value) ? 0 : value; value++; - document.getElementById(id).value = value; + element.value = value; + + window.dispatchChanged(element); }; window.decrementValue = function (id) { - let value = parseInt(document.getElementById(id).value); + const element = document.getElementById(id); + let value = parseInt(element.value); value = isNaN(value) ? 0 : value; value--; value = value < 0 ? 0 : value; - document.getElementById(id).value = value; + element.value = value; + + window.dispatchChanged(element); +}; + +window.dispatchChanged = function (element) { + const event = new Event("change", { bubbles: true }); + element.dispatchEvent(event); }; diff --git a/src/purchase/urls.py b/src/purchase/urls.py index 0f60b6b..263f42f 100644 --- a/src/purchase/urls.py +++ b/src/purchase/urls.py @@ -1,12 +1,19 @@ from django.urls import path -from purchase.views import delete_basket, list_baskets, new_basket, update_basket -from purchase.views.basket import additional_unpriced_product +from purchase.views import ( + additional_unpriced_product, + delete_basket, + list_baskets, + new_basket, + price_preview, + update_basket, +) from purchase.views.reports import by_hour_plot_view, products_plots_view, reports app_name = "purchase" urlpatterns = [ path("", list_baskets, name="list"), + path("price_preview/", price_preview, name="price_preview"), path("new/", new_basket, name="new"), path("/update/", update_basket, name="update"), path("/delete/", delete_basket, name="delete"), diff --git a/src/purchase/views/__init__.py b/src/purchase/views/__init__.py index 9b31af1..4a3a467 100644 --- a/src/purchase/views/__init__.py +++ b/src/purchase/views/__init__.py @@ -1,3 +1,17 @@ -from .basket import delete_basket, list_baskets, new_basket, update_basket +from .basket import ( + additional_unpriced_product, + delete_basket, + list_baskets, + new_basket, + price_preview, + update_basket, +) -__all__ = ["new_basket", "update_basket", "delete_basket", "list_baskets"] +__all__ = [ + "new_basket", + "update_basket", + "delete_basket", + "list_baskets", + "additional_unpriced_product", + "price_preview", +] diff --git a/src/purchase/views/basket.py b/src/purchase/views/basket.py index 5e22bb1..fd445b0 100644 --- a/src/purchase/views/basket.py +++ b/src/purchase/views/basket.py @@ -3,7 +3,7 @@ import logging from django.contrib import messages from django.contrib.auth.decorators import permission_required from django.core.handlers.wsgi import WSGIRequest -from django.http import HttpRequest, HttpResponse +from django.http import HttpResponse from django.shortcuts import get_object_or_404, redirect, render from django.template.response import TemplateResponse from django.urls import reverse @@ -12,7 +12,7 @@ from django.utils.translation import gettext_lazy as _ from django.views.decorators.http import condition, require_http_methods from django_htmx.http import trigger_client_event -from purchase.forms import UNPRICED_PREFIX, BasketForm +from purchase.forms import PRICED_PREFIX, UNPRICED_PREFIX, BasketForm from purchase.models import Basket, Product, reports_etag, reports_last_modified logger = logging.getLogger(__name__) @@ -100,14 +100,14 @@ def additional_unpriced_product(request: WSGIRequest) -> HttpResponse: @permission_required("purchase.view_basket") @condition(etag_func=reports_etag, last_modified_func=reports_last_modified) -def list_baskets(request: HttpRequest) -> HttpResponse: +def list_baskets(request: WSGIRequest) -> HttpResponse: context = {"baskets": Basket.objects.priced().order_by("-id")} return TemplateResponse(request, "purchase/basket_list.html", context) @require_http_methods(["GET", "POST"]) @permission_required("purchase.delete_basket") -def delete_basket(request: HttpRequest, pk: int) -> HttpResponse: +def delete_basket(request: WSGIRequest, pk: int) -> HttpResponse: basket = get_object_or_404(Basket, pk=pk) if request.method == "GET": context = {"basket": basket} @@ -115,3 +115,18 @@ def delete_basket(request: HttpRequest, pk: int) -> HttpResponse: basket.delete() messages.success(request, _("Basket successfully deleted.")) return redirect("purchase:list") + + +@require_http_methods(["POST"]) +@permission_required("purchase.add_basket") +def price_preview(request: WSGIRequest) -> HttpResponse: + total = 0 + for name in request.POST: + if name.startswith(PRICED_PREFIX): + product_id = name[len(PRICED_PREFIX) :] + product = get_object_or_404(Product, pk=product_id) + total += product.unit_price_cents * int(request.POST.get(name, 0)) + elif name.startswith(UNPRICED_PREFIX): + total += sum(map(int, request.POST.getlist(name))) + + return HttpResponse(f"Montant total : {total/100:.2f}€")