From 91d64c6ce482ceb7e11cac905ad0ce10b7fa1b1c Mon Sep 17 00:00:00 2001 From: Gabriel Augendre Date: Wed, 4 May 2022 22:24:05 +0200 Subject: [PATCH] Replace reports CBV with FBV --- src/purchase/urls.py | 4 +- src/purchase/views/reports.py | 284 +++++++++++++++++----------------- 2 files changed, 143 insertions(+), 145 deletions(-) diff --git a/src/purchase/urls.py b/src/purchase/urls.py index e7531d8..532ea5a 100644 --- a/src/purchase/urls.py +++ b/src/purchase/urls.py @@ -6,7 +6,7 @@ from purchase.views import ( NewBasketView, UpdateBasketView, ) -from purchase.views.reports import ReportsView +from purchase.views.reports import reports app_name = "purchase" urlpatterns = [ @@ -14,5 +14,5 @@ urlpatterns = [ path("new/", NewBasketView.as_view(), name="new"), path("/update/", UpdateBasketView.as_view(), name="update"), path("/delete/", DeleteBasketView.as_view(), name="delete"), - path("reports/", ReportsView.as_view(), name="reports"), + path("reports/", reports, name="reports"), ] diff --git a/src/purchase/views/reports.py b/src/purchase/views/reports.py index 15980db..2e4ab34 100644 --- a/src/purchase/views/reports.py +++ b/src/purchase/views/reports.py @@ -5,8 +5,9 @@ from zoneinfo import ZoneInfo import numpy as np from django.conf import settings from django.contrib import messages +from django.contrib.auth.decorators import permission_required +from django.template.response import TemplateResponse from django.utils.translation import gettext as _ -from django.views.generic import TemplateView from matplotlib import pyplot as plt from matplotlib import ticker from matplotlib.axes import Axes @@ -15,167 +16,164 @@ from matplotlib.dates import AutoDateLocator, ConciseDateFormatter, HourLocator from matplotlib.figure import Figure from purchase.models import Basket, PaymentMethod, Product, ProductQuerySet -from purchase.views.utils import ProtectedViewsMixin -class ReportsView(ProtectedViewsMixin, TemplateView): - permission_required = ["purchase.view_basket"] +@permission_required("purchase.view_basket") +def reports(request): template_name = "purchase/reports.html" + baskets = list(Basket.objects.priced().order_by("created_at")) + if not baskets: + messages.warning(request, _("No sale to report")) + return TemplateResponse(request, template_name, {}) - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - baskets = list(Basket.objects.priced().order_by("created_at")) - if not baskets: - messages.warning(self.request, _("No sale to report")) - return context + dates = Basket.objects.values_list("created_at__date", flat=True).distinct() + average_basket_by_day = { + date: Basket.objects.by_date(date).average_basket() for date in dates + } + turnover_by_day = {date: Basket.objects.by_date(date).turnover() for date in dates} - dates = Basket.objects.values_list("created_at__date", flat=True).distinct() - average_basket_by_day = { - date: Basket.objects.by_date(date).average_basket() for date in dates - } - turnover_by_day = { - date: Basket.objects.by_date(date).turnover() for date in dates - } + products = Product.objects.with_turnover().with_sold() + ( + products_plot, + products_sold_pie, + products_turnover_pie, + ) = get_products_plots(products) - products = Product.objects.with_turnover().with_sold() - ( - products_plot, - products_sold_pie, - products_turnover_pie, - ) = self.get_products_plots(products) - by_hour_plot = self.by_hour_plot(baskets) - context.update( - { - "turnover": Basket.objects.turnover(), - "turnover_by_day": turnover_by_day, - "average_basket": Basket.objects.average_basket(), - "average_basket_by_day": average_basket_by_day, - "products": products, - "products_plot": products_plot, - "products_sold_pie": products_sold_pie, - "products_turnover_pie": products_turnover_pie, - "by_hour_plot": by_hour_plot, - "payment_methods": PaymentMethod.objects.with_turnover().with_sold(), - "no_payment_method": Basket.objects.no_payment_method().priced(), - } - ) - return context + context = { + "turnover": Basket.objects.turnover(), + "turnover_by_day": turnover_by_day, + "average_basket": Basket.objects.average_basket(), + "average_basket_by_day": average_basket_by_day, + "products": products, + "products_plot": products_plot, + "products_sold_pie": products_sold_pie, + "products_turnover_pie": products_turnover_pie, + "by_hour_plot": by_hour_plot(baskets), + "payment_methods": PaymentMethod.objects.with_turnover().with_sold(), + "no_payment_method": Basket.objects.no_payment_method().priced(), + } + return TemplateResponse(request, template_name, context) - def get_products_plots(self, products: ProductQuerySet): - labels, sold, turnover = self.get_products_data_for_plot(products) - x = np.arange(len(labels)) - width = 0.4 - fig: Figure = plt.figure() - fig.suptitle(_("Sales by product")) +def get_products_plots(products: ProductQuerySet): + labels, sold, turnover = get_products_data_for_plot(products) - color = "tab:orange" - ax1: Axes = fig.add_subplot() - bar: BarContainer = ax1.bar(x - width / 2, sold, width=width, color=color) - ax1.bar_label(bar) - ax1.tick_params(axis="x", rotation=15) - ax1.tick_params(axis="y", labelcolor=color) - ax1.set_xticks(x, labels) - ax1.set_ylabel(_("# sold"), color=color) + x = np.arange(len(labels)) + width = 0.4 + fig: Figure = plt.figure() + fig.suptitle(_("Sales by product")) - color = "tab:blue" - ax2: Axes = ax1.twinx() - bar = ax2.bar(x + width / 2, turnover, width=width, color=color) - ax2.bar_label(bar, fmt="%d€") - ax2.set_ylabel(_("Turnover by product"), color=color) - ax2.yaxis.set_major_formatter(ticker.FormatStrFormatter("%.2f€")) - ax2.tick_params(axis="y", labelcolor=color) + color = "tab:orange" + ax1: Axes = fig.add_subplot() + bar: BarContainer = ax1.bar(x - width / 2, sold, width=width, color=color) + ax1.bar_label(bar) + ax1.tick_params(axis="x", rotation=15) + ax1.tick_params(axis="y", labelcolor=color) + ax1.set_xticks(x, labels) + ax1.set_ylabel(_("# sold"), color=color) - fig.tight_layout() - img1 = self.get_image_from_fig(fig) + color = "tab:blue" + ax2: Axes = ax1.twinx() + bar = ax2.bar(x + width / 2, turnover, width=width, color=color) + ax2.bar_label(bar, fmt="%d€") + ax2.set_ylabel(_("Turnover by product"), color=color) + ax2.yaxis.set_major_formatter(ticker.FormatStrFormatter("%.2f€")) + ax2.tick_params(axis="y", labelcolor=color) - fig = plt.figure() - fig.suptitle(_("# sold")) - ax1 = fig.add_subplot() - ax1.pie(sold, labels=labels, autopct="%d%%") - fig.tight_layout() - img2 = self.get_image_from_fig(fig) + fig.tight_layout() + img1 = get_image_from_fig(fig) - fig = plt.figure() - fig.suptitle(_("Turnover by product")) - ax1 = fig.add_subplot() - ax1.pie(turnover, labels=labels, autopct="%d%%") - fig.tight_layout() - img3 = self.get_image_from_fig(fig) + fig = plt.figure() + fig.suptitle(_("# sold")) + ax1 = fig.add_subplot() + ax1.pie(sold, labels=labels, autopct="%d%%") + fig.tight_layout() + img2 = get_image_from_fig(fig) - return img1, img2, img3 + fig = plt.figure() + fig.suptitle(_("Turnover by product")) + ax1 = fig.add_subplot() + ax1.pie(turnover, labels=labels, autopct="%d%%") + fig.tight_layout() + img3 = get_image_from_fig(fig) - def get_products_data_for_plot(self, products): - labels = [] - sold = [] - turnover = [] - for product in products: - labels.append(product.name) - sold.append(product.sold) - turnover.append(product.turnover / 100) - return labels, sold, turnover + return img1, img2, img3 - def by_hour_plot(self, baskets): - labels, counts, turnovers = self.get_by_hour_data_for_plot(baskets) - hours_in_day = 24 - fig: Figure = plt.figure() - fig.suptitle(_("Sales by hour")) - ax1: Axes = fig.add_subplot() - color = "tab:orange" - ax1.bar(labels, counts, width=1 / hours_in_day, color=color) - ax1.tick_params(axis="x", rotation=15) - ax1.xaxis.set_minor_locator(HourLocator()) - tz = ZoneInfo(settings.TIME_ZONE) - locator = AutoDateLocator(tz=tz) - ax1.xaxis.set_major_locator(locator) - ax1.xaxis.set_major_formatter(ConciseDateFormatter(locator, tz=tz)) - ax1.set_ylabel(_("Basket count by hour"), color=color) - ax1.set_yticks(np.linspace(*ax1.get_ybound(), 8)) - ax1.tick_params(axis="y", labelcolor=color) - ax1.grid(visible=True, which="major", axis="y") +def get_products_data_for_plot(products): + labels = [] + sold = [] + turnover = [] + for product in products: + labels.append(product.name) + sold.append(product.sold) + turnover.append(product.turnover / 100) + return labels, sold, turnover - color = "tab:blue" - ax2: Axes = ax1.twinx() - ax2.plot(labels, turnovers, ".-", color=color) - ax2.set_ylabel(_("Turnover by hour"), color=color) - ax2.set_ylim(bottom=0) - ax2.set_yticks(np.linspace(*ax2.get_ybound(), 8)) - ax2.tick_params(axis="y", labelcolor=color) - ax2.yaxis.set_major_formatter(ticker.FormatStrFormatter("%.2f€")) - fig.tight_layout() - return self.get_image_from_fig(fig) +def by_hour_plot(baskets): + labels, counts, turnovers = get_by_hour_data_for_plot(baskets) + hours_in_day = 24 + fig: Figure = plt.figure() + fig.suptitle(_("Sales by hour")) + ax1: Axes = fig.add_subplot() - def get_by_hour_data_for_plot(self, baskets): - current: datetime.datetime = baskets[0].created_at - current = current.replace(minute=0, second=0, microsecond=0) - end: datetime.datetime = baskets[-1].created_at - basket_index = 0 - labels = [] - counts = [] - turnovers = [] - while current < end: - end_slot = current + datetime.timedelta(hours=1) + color = "tab:orange" + ax1.bar(labels, counts, width=1 / hours_in_day, color=color) + ax1.tick_params(axis="x", rotation=15) + ax1.xaxis.set_minor_locator(HourLocator()) + tz = ZoneInfo(settings.TIME_ZONE) + locator = AutoDateLocator(tz=tz) + ax1.xaxis.set_major_locator(locator) + ax1.xaxis.set_major_formatter(ConciseDateFormatter(locator, tz=tz)) + ax1.set_ylabel(_("Basket count by hour"), color=color) + ax1.set_yticks(np.linspace(*ax1.get_ybound(), 8)) + ax1.tick_params(axis="y", labelcolor=color) + ax1.grid(visible=True, which="major", axis="y") + + color = "tab:blue" + ax2: Axes = ax1.twinx() + ax2.plot(labels, turnovers, ".-", color=color) + ax2.set_ylabel(_("Turnover by hour"), color=color) + ax2.set_ylim(bottom=0) + ax2.set_yticks(np.linspace(*ax2.get_ybound(), 8)) + ax2.tick_params(axis="y", labelcolor=color) + ax2.yaxis.set_major_formatter(ticker.FormatStrFormatter("%.2f€")) + + fig.tight_layout() + return get_image_from_fig(fig) + + +def get_by_hour_data_for_plot(baskets): + current: datetime.datetime = baskets[0].created_at + current = current.replace(minute=0, second=0, microsecond=0) + end: datetime.datetime = baskets[-1].created_at + basket_index = 0 + labels = [] + counts = [] + turnovers = [] + while current < end: + end_slot = current + datetime.timedelta(hours=1) + basket = baskets[basket_index] + count = 0 + turnover = 0 + while basket.created_at < end_slot: + count += 1 + turnover += basket.price / 100 + basket_index += 1 + if basket_index == len(baskets): + break basket = baskets[basket_index] - count = 0 - turnover = 0 - while basket.created_at < end_slot: - count += 1 - turnover += basket.price / 100 - basket_index += 1 - if basket_index == len(baskets): - break - basket = baskets[basket_index] - labels.append(current.astimezone(ZoneInfo(settings.TIME_ZONE))) - counts.append(count) - turnovers.append(turnover) - current = end_slot - return labels, counts, turnovers + labels.append(current.astimezone(ZoneInfo(settings.TIME_ZONE))) + counts.append(count) + turnovers.append(turnover) + current = end_slot + return labels, counts, turnovers - def get_image_from_fig(self, fig): - image_data = StringIO() - fig.savefig(image_data, format="svg") - image_data.seek(0) - img1 = image_data.getvalue() - return img1 + +def get_image_from_fig(fig): + image_data = StringIO() + fig.savefig(image_data, format="svg") + image_data.seek(0) + img1 = image_data.getvalue() + return img1