From af48553e35c47e974371ea51edb183df786defb6 Mon Sep 17 00:00:00 2001 From: Bartuccio Antoine Date: Wed, 27 Nov 2019 16:23:14 +0100 Subject: [PATCH 1/7] club: separation between archived products and non archived ones --- club/forms.py | 16 +++++++++- club/templates/club/club_sellings.jinja | 2 +- club/views.py | 39 +++++++++++++++---------- 3 files changed, 39 insertions(+), 18 deletions(-) diff --git a/club/forms.py b/club/forms.py index 4f5d9b85..865d66a0 100644 --- a/club/forms.py +++ b/club/forms.py @@ -157,7 +157,7 @@ class MailingForm(forms.Form): return cleaned_data -class SellingsFormBase(forms.Form): +class SellingsForm(forms.Form): begin_date = forms.DateTimeField( input_formats=["%Y-%m-%d %H:%M:%S"], label=_("Begin date"), @@ -174,6 +174,20 @@ class SellingsFormBase(forms.Form): Counter.objects.order_by("name").all(), label=_("Counter"), required=False ) + def __init__(self, club, *args, **kwargs): + + super(SellingsForm, self).__init__(*args, **kwargs) + self.fields["product"] = forms.ModelChoiceField( + club.products.order_by("name").filter(archived=False).all(), + label=_("Product"), + required=False, + ) + self.fields["archived_product"] = forms.ModelChoiceField( + club.products.order_by("name").filter(archived=True).all(), + label=_("Archived product"), + required=False, + ) + class ClubMemberForm(forms.Form): """ diff --git a/club/templates/club/club_sellings.jinja b/club/templates/club/club_sellings.jinja index c5da0e61..56065ac1 100644 --- a/club/templates/club/club_sellings.jinja +++ b/club/templates/club/club_sellings.jinja @@ -3,7 +3,7 @@ {% block content %}

{% trans %}Sellings{% endtrans %}

-
+ {% csrf_token %} {{ form }}

diff --git a/club/views.py b/club/views.py index 5a69df7e..b262d57c 100644 --- a/club/views.py +++ b/club/views.py @@ -60,7 +60,7 @@ from com.views import ( ) from club.models import Club, Membership, Mailing, MailingSubscription -from club.forms import MailingForm, ClubEditForm, ClubMemberForm, SellingsFormBase +from club.forms import MailingForm, ClubEditForm, ClubMemberForm, SellingsForm class ClubTabsMixin(TabedViewMixin): @@ -319,7 +319,7 @@ class ClubOldMembersView(ClubTabsMixin, CanViewMixin, DetailView): current_tab = "elderlies" -class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailView): +class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailFormView): """ Sellings of a club """ @@ -328,21 +328,26 @@ class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailView): pk_url_kwarg = "club_id" template_name = "club/club_sellings.jinja" current_tab = "sellings" + form_class = SellingsForm - def get_form_class(self): - kwargs = { - "product": forms.ModelChoiceField( - self.object.products.order_by("name").all(), - label=_("Product"), - required=False, - ) - } - return type("SellingsForm", (SellingsFormBase,), kwargs) + def get_form_kwargs(self): + kwargs = super(ClubSellingView, self).get_form_kwargs() + kwargs["club"] = self.object + return kwargs + + def post(self, request, *args, **kwargs): + return self.get(request, *args, **kwargs) def get_context_data(self, **kwargs): kwargs = super(ClubSellingView, self).get_context_data(**kwargs) - form = self.get_form_class()(self.request.GET) qs = Selling.objects.filter(club=self.object) + + kwargs["result"] = qs[:0] + kwargs["total"] = 0 + kwargs["total_quantity"] = 0 + kwargs["benefit"] = 0 + + form = self.get_form() if form.is_valid(): if not len([v for v in form.cleaned_data.values() if v is not None]): qs = Selling.objects.filter(id=-1) @@ -352,17 +357,19 @@ class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailView): qs = qs.filter(date__lte=form.cleaned_data["end_date"]) if form.cleaned_data["counter"]: qs = qs.filter(counter=form.cleaned_data["counter"]) + selected_products = [] if form.cleaned_data["product"]: - qs = qs.filter(product__id=form.cleaned_data["product"].id) + selected_products.append(form.cleaned_data["product"].id) + if form.cleaned_data["archived_product"]: + selected_products.append(form.cleaned_data["selected_products"].id) + if len(selected_products) > 0: + qs = qs.filter(product__id__in=selected_products) kwargs["result"] = qs.all().order_by("-id") kwargs["total"] = sum([s.quantity * s.unit_price for s in qs.all()]) kwargs["total_quantity"] = sum([s.quantity for s in qs.all()]) kwargs["benefit"] = kwargs["total"] - sum( [s.product.purchase_price for s in qs.exclude(product=None)] ) - else: - kwargs["result"] = qs[:0] - kwargs["form"] = form return kwargs From 7d40e111449070111a1515145135a6e4a07ee989 Mon Sep 17 00:00:00 2001 From: Bartuccio Antoine Date: Wed, 27 Nov 2019 20:37:28 +0100 Subject: [PATCH 2/7] club: ClubSellingView way faster and with multiple selections everywhere --- club/forms.py | 10 +++++----- club/views.py | 32 ++++++++++++++++++++------------ 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/club/forms.py b/club/forms.py index 865d66a0..3f450f78 100644 --- a/club/forms.py +++ b/club/forms.py @@ -170,21 +170,21 @@ class SellingsForm(forms.Form): required=False, widget=SelectDateTime, ) - counter = forms.ModelChoiceField( + counters = forms.ModelMultipleChoiceField( Counter.objects.order_by("name").all(), label=_("Counter"), required=False ) def __init__(self, club, *args, **kwargs): super(SellingsForm, self).__init__(*args, **kwargs) - self.fields["product"] = forms.ModelChoiceField( + self.fields["products"] = forms.ModelMultipleChoiceField( club.products.order_by("name").filter(archived=False).all(), - label=_("Product"), + label=_("Products"), required=False, ) - self.fields["archived_product"] = forms.ModelChoiceField( + self.fields["archived_products"] = forms.ModelMultipleChoiceField( club.products.order_by("name").filter(archived=True).all(), - label=_("Archived product"), + label=_("Archived products"), required=False, ) diff --git a/club/views.py b/club/views.py index b262d57c..00fa565d 100644 --- a/club/views.py +++ b/club/views.py @@ -355,21 +355,29 @@ class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailFormView): qs = qs.filter(date__gte=form.cleaned_data["begin_date"]) if form.cleaned_data["end_date"]: qs = qs.filter(date__lte=form.cleaned_data["end_date"]) - if form.cleaned_data["counter"]: - qs = qs.filter(counter=form.cleaned_data["counter"]) + + if form.cleaned_data["counters"]: + qs = qs.filter(counter__in=form.cleaned_data["counters"]) + selected_products = [] - if form.cleaned_data["product"]: - selected_products.append(form.cleaned_data["product"].id) - if form.cleaned_data["archived_product"]: - selected_products.append(form.cleaned_data["selected_products"].id) + if form.cleaned_data["products"]: + selected_products.extend(form.cleaned_data["products"]) + if form.cleaned_data["archived_products"]: + selected_products.extend(form.cleaned_data["selected_products"]) + + print(selected_products) + if len(selected_products) > 0: - qs = qs.filter(product__id__in=selected_products) + qs = qs.filter(product__in=selected_products) + kwargs["result"] = qs.all().order_by("-id") - kwargs["total"] = sum([s.quantity * s.unit_price for s in qs.all()]) - kwargs["total_quantity"] = sum([s.quantity for s in qs.all()]) - kwargs["benefit"] = kwargs["total"] - sum( - [s.product.purchase_price for s in qs.exclude(product=None)] - ) + for selling in kwargs["result"]: + kwargs["total"] += selling.quantity * selling.unit_price + kwargs["total_quantity"] += selling.quantity + if hasattr(selling, "product"): + kwargs["benefit"] += selling.product.purchase_price + + kwargs["benefit"] = kwargs["total"] - kwargs["benefit"] return kwargs From a73f5cb270eb02f7125baf4fee37cdf389dbbfad Mon Sep 17 00:00:00 2001 From: Bartuccio Antoine Date: Wed, 27 Nov 2019 21:25:47 +0100 Subject: [PATCH 3/7] club: use sums in bdd for ClubSellingView --- club/views.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/club/views.py b/club/views.py index 00fa565d..e2bee9c0 100644 --- a/club/views.py +++ b/club/views.py @@ -37,6 +37,8 @@ from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext as _t from django.core.exceptions import PermissionDenied, ValidationError, NON_FIELD_ERRORS from django.shortcuts import get_object_or_404, redirect +from django.db.models import Sum + from core.views import ( CanCreateMixin, @@ -365,19 +367,20 @@ class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailFormView): if form.cleaned_data["archived_products"]: selected_products.extend(form.cleaned_data["selected_products"]) - print(selected_products) - if len(selected_products) > 0: qs = qs.filter(product__in=selected_products) kwargs["result"] = qs.all().order_by("-id") - for selling in kwargs["result"]: - kwargs["total"] += selling.quantity * selling.unit_price - kwargs["total_quantity"] += selling.quantity - if hasattr(selling, "product"): - kwargs["benefit"] += selling.product.purchase_price + kwargs["total"] = sum([s.quantity * s.unit_price for s in kwargs["result"]]) + total_quantity = qs.all().aggregate(Sum("quantity")) + if total_quantity["quantity__sum"]: + kwargs["total_quantity"] = total_quantity["quantity__sum"] + benefit = ( + qs.exclude(product=None).all().aggregate(Sum("product__purchase_price")) + ) + if benefit["product__purchase_price__sum"]: + kwargs["benefit"] = benefit["product__purchase_price__sum"] - kwargs["benefit"] = kwargs["total"] - kwargs["benefit"] return kwargs From 8dd2c02d3ea9909f76d498da6c19719c921cdc86 Mon Sep 17 00:00:00 2001 From: Bartuccio Antoine Date: Thu, 28 Nov 2019 00:30:51 +0100 Subject: [PATCH 4/7] club: add pagination for ClubSellingView --- club/templates/club/club_sellings.jinja | 5 +++-- club/views.py | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/club/templates/club/club_sellings.jinja b/club/templates/club/club_sellings.jinja index 56065ac1..1062f8cb 100644 --- a/club/templates/club/club_sellings.jinja +++ b/club/templates/club/club_sellings.jinja @@ -1,9 +1,9 @@ {% extends "core/base.jinja" %} -{% from 'core/macros.jinja' import user_profile_link %} +{% from 'core/macros.jinja' import user_profile_link, paginate %} {% block content %}

{% trans %}Sellings{% endtrans %}

- + {% csrf_token %} {{ form }}

@@ -53,6 +53,7 @@ {% endfor %} +{{ paginate(result, paginator) }} {% endblock %} diff --git a/club/views.py b/club/views.py index e2bee9c0..d90d51bf 100644 --- a/club/views.py +++ b/club/views.py @@ -36,6 +36,7 @@ from django.utils import timezone from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext as _t from django.core.exceptions import PermissionDenied, ValidationError, NON_FIELD_ERRORS +from django.core.paginator import Paginator, InvalidPage from django.shortcuts import get_object_or_404, redirect from django.db.models import Sum @@ -331,6 +332,14 @@ class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailFormView): template_name = "club/club_sellings.jinja" current_tab = "sellings" form_class = SellingsForm + paginate_by = 70 + + def dispatch(self, request, *args, **kwargs): + try: + self.asked_page = int(request.GET.get("page", 1)) + except ValueError: + raise Http404 + return super(ClubSellingView, self).dispatch(request, *args, **kwargs) def get_form_kwargs(self): kwargs = super(ClubSellingView, self).get_form_kwargs() @@ -381,6 +390,12 @@ class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailFormView): if benefit["product__purchase_price__sum"]: kwargs["benefit"] = benefit["product__purchase_price__sum"] + kwargs["paginator"] = Paginator(kwargs["result"], self.paginate_by) + try: + kwargs["result"] = kwargs["paginator"].page(self.asked_page) + except InvalidPage: + raise Http404 + return kwargs From 274a7b7137cc08f48d867a44f3169c0896acca31 Mon Sep 17 00:00:00 2001 From: Bartuccio Antoine Date: Thu, 28 Nov 2019 01:15:57 +0100 Subject: [PATCH 5/7] core/club: allow adding custom js action to pagination link, useful for FormDetailView with pagination --- club/templates/club/club_sellings.jinja | 13 ++++++++++--- club/views.py | 3 ++- core/templates/core/macros.jinja | 9 +++++---- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/club/templates/club/club_sellings.jinja b/club/templates/club/club_sellings.jinja index 1062f8cb..6759fb3d 100644 --- a/club/templates/club/club_sellings.jinja +++ b/club/templates/club/club_sellings.jinja @@ -3,7 +3,7 @@ {% block content %}

{% trans %}Sellings{% endtrans %}

- + {% csrf_token %} {{ form }}

@@ -28,7 +28,7 @@ - {% for s in result %} + {% for s in paginated_result %} {{ s.date|localtime|date(DATETIME_FORMAT) }} {{ s.date|localtime|time(DATETIME_FORMAT) }} {{ s.counter }} @@ -53,7 +53,14 @@ {% endfor %} -{{ paginate(result, paginator) }} + +{{ paginate(paginated_result, paginator, "formPagination(this)") }} {% endblock %} diff --git a/club/views.py b/club/views.py index d90d51bf..cf7cab01 100644 --- a/club/views.py +++ b/club/views.py @@ -354,6 +354,7 @@ class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailFormView): qs = Selling.objects.filter(club=self.object) kwargs["result"] = qs[:0] + kwargs["paginated_result"] = kwargs["result"] kwargs["total"] = 0 kwargs["total_quantity"] = 0 kwargs["benefit"] = 0 @@ -392,7 +393,7 @@ class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailFormView): kwargs["paginator"] = Paginator(kwargs["result"], self.paginate_by) try: - kwargs["result"] = kwargs["paginator"].page(self.asked_page) + kwargs["paginated_result"] = kwargs["paginator"].page(self.asked_page) except InvalidPage: raise Http404 diff --git a/core/templates/core/macros.jinja b/core/templates/core/macros.jinja index d9dd68c2..965c6f11 100644 --- a/core/templates/core/macros.jinja +++ b/core/templates/core/macros.jinja @@ -113,10 +113,11 @@ {% endif %} {% endmacro %} -{% macro paginate(page_obj, paginator) %} +{% macro paginate(page_obj, paginator, js_action) %} + {% set js = js_action|default('') %} {% if page_obj.has_previous() or page_obj.has_next() %} {% if page_obj.has_previous() %} - {% trans %}Previous{% endtrans %} + {% trans %}Previous{% endtrans %} {% else %} {% trans %}Previous{% endtrans %} {% endif %} @@ -124,11 +125,11 @@ {% if page_obj.number == i %} {{ i }} ({% trans %}current{% endtrans %}) {% else %} - {{ i }} + {{ i }} {% endif %} {% endfor %} {% if page_obj.has_next() %} - {% trans %}Next{% endtrans %} + {% trans %}Next{% endtrans %} {% else %} {% trans %}Next{% endtrans %} {% endif %} From bf5fc8750d863d58e1da1f3a1afa067861419825 Mon Sep 17 00:00:00 2001 From: Bartuccio Antoine Date: Thu, 28 Nov 2019 14:52:33 +0100 Subject: [PATCH 6/7] club: steam CSV download for SellingView --- club/views.py | 84 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 30 deletions(-) diff --git a/club/views.py b/club/views.py index cf7cab01..f86e6999 100644 --- a/club/views.py +++ b/club/views.py @@ -23,6 +23,7 @@ # # +import csv from django.conf import settings from django import forms @@ -30,7 +31,12 @@ from django.views.generic import ListView, DetailView, TemplateView, View from django.views.generic.edit import DeleteView from django.views.generic.detail import SingleObjectMixin from django.views.generic.edit import UpdateView, CreateView -from django.http import HttpResponseRedirect, HttpResponse, Http404 +from django.http import ( + HttpResponseRedirect, + HttpResponse, + Http404, + StreamingHttpResponse, +) from django.urls import reverse, reverse_lazy from django.utils import timezone from django.utils.translation import ugettext_lazy as _ @@ -405,16 +411,46 @@ class ClubSellingCSVView(ClubSellingView): Generate sellings in csv for a given period """ - def get(self, request, *args, **kwargs): - import csv + class StreamWriter: + """Implements a file-like interface for streaming the CSV""" + + def write(self, value): + """Write the value by returning it, instead of storing in a buffer.""" + return value + + def write_selling(self, selling): + row = [selling.date, selling.counter] + if selling.seller: + row.append(selling.seller.get_display_name()) + else: + row.append("") + if selling.customer: + row.append(selling.customer.user.get_display_name()) + else: + row.append("") + row = row + [ + selling.label, + selling.quantity, + selling.quantity * selling.unit_price, + selling.get_payment_method_display(), + ] + if selling.product: + row.append(selling.product.selling_price) + row.append(selling.product.purchase_price) + row.append(selling.product.selling_price - selling.product.purchase_price) + else: + row = row + ["", "", ""] + return row + + def get(self, request, *args, **kwargs): - response = HttpResponse(content_type="text/csv") self.object = self.get_object() - name = _("Sellings") + "_" + self.object.name + ".csv" - response["Content-Disposition"] = "filename=" + name kwargs = self.get_context_data(**kwargs) + + # Use the StreamWriter class instead of request for streaming + pseudo_buffer = self.StreamWriter() writer = csv.writer( - response, delimiter=";", lineterminator="\n", quoting=csv.QUOTE_ALL + pseudo_buffer, delimiter=";", lineterminator="\n", quoting=csv.QUOTE_ALL ) writer.writerow([_t("Quantity"), kwargs["total_quantity"]]) @@ -435,29 +471,17 @@ class ClubSellingCSVView(ClubSellingView): _t("Benefit"), ] ) - for o in kwargs["result"]: - row = [o.date, o.counter] - if o.seller: - row.append(o.seller.get_display_name()) - else: - row.append("") - if o.customer: - row.append(o.customer.user.get_display_name()) - else: - row.append("") - row = row + [ - o.label, - o.quantity, - o.quantity * o.unit_price, - o.get_payment_method_display(), - ] - if o.product: - row.append(o.product.selling_price) - row.append(o.product.purchase_price) - row.append(o.product.selling_price - o.product.purchase_price) - else: - row = row + ["", "", ""] - writer.writerow(row) + + # Stream response + response = StreamingHttpResponse( + ( + writer.writerow(self.write_selling(selling)) + for selling in kwargs["result"] + ), + content_type="text/csv", + ) + name = _("Sellings") + "_" + self.object.name + ".csv" + response["Content-Disposition"] = "filename=" + name return response From 4a78157f9a95d064487f5423a6527b4c3f47226f Mon Sep 17 00:00:00 2001 From: Bartuccio Antoine Date: Thu, 28 Nov 2019 15:14:51 +0100 Subject: [PATCH 7/7] club: fix typo on ClubSellingView --- club/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/club/views.py b/club/views.py index f86e6999..73c06dd8 100644 --- a/club/views.py +++ b/club/views.py @@ -381,7 +381,7 @@ class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailFormView): if form.cleaned_data["products"]: selected_products.extend(form.cleaned_data["products"]) if form.cleaned_data["archived_products"]: - selected_products.extend(form.cleaned_data["selected_products"]) + selected_products.extend(form.cleaned_data["archived_products"]) if len(selected_products) > 0: qs = qs.filter(product__in=selected_products)