Replace reports CBV with FBV

This commit is contained in:
Gabriel Augendre 2022-05-04 22:24:05 +02:00
parent 60660c0abf
commit 91d64c6ce4
2 changed files with 143 additions and 145 deletions

View file

@ -6,7 +6,7 @@ from purchase.views import (
NewBasketView, NewBasketView,
UpdateBasketView, UpdateBasketView,
) )
from purchase.views.reports import ReportsView from purchase.views.reports import reports
app_name = "purchase" app_name = "purchase"
urlpatterns = [ urlpatterns = [
@ -14,5 +14,5 @@ urlpatterns = [
path("new/", NewBasketView.as_view(), name="new"), path("new/", NewBasketView.as_view(), name="new"),
path("<int:pk>/update/", UpdateBasketView.as_view(), name="update"), path("<int:pk>/update/", UpdateBasketView.as_view(), name="update"),
path("<int:pk>/delete/", DeleteBasketView.as_view(), name="delete"), path("<int:pk>/delete/", DeleteBasketView.as_view(), name="delete"),
path("reports/", ReportsView.as_view(), name="reports"), path("reports/", reports, name="reports"),
] ]

View file

@ -5,8 +5,9 @@ from zoneinfo import ZoneInfo
import numpy as np import numpy as np
from django.conf import settings from django.conf import settings
from django.contrib import messages 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.utils.translation import gettext as _
from django.views.generic import TemplateView
from matplotlib import pyplot as plt from matplotlib import pyplot as plt
from matplotlib import ticker from matplotlib import ticker
from matplotlib.axes import Axes from matplotlib.axes import Axes
@ -15,167 +16,164 @@ from matplotlib.dates import AutoDateLocator, ConciseDateFormatter, HourLocator
from matplotlib.figure import Figure from matplotlib.figure import Figure
from purchase.models import Basket, PaymentMethod, Product, ProductQuerySet 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" 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): dates = Basket.objects.values_list("created_at__date", flat=True).distinct()
context = super().get_context_data(**kwargs) average_basket_by_day = {
baskets = list(Basket.objects.priced().order_by("created_at")) date: Basket.objects.by_date(date).average_basket() for date in dates
if not baskets: }
messages.warning(self.request, _("No sale to report")) turnover_by_day = {date: Basket.objects.by_date(date).turnover() for date in dates}
return context
dates = Basket.objects.values_list("created_at__date", flat=True).distinct() products = Product.objects.with_turnover().with_sold()
average_basket_by_day = { (
date: Basket.objects.by_date(date).average_basket() for date in dates products_plot,
} products_sold_pie,
turnover_by_day = { products_turnover_pie,
date: Basket.objects.by_date(date).turnover() for date in dates ) = get_products_plots(products)
}
products = Product.objects.with_turnover().with_sold() context = {
( "turnover": Basket.objects.turnover(),
products_plot, "turnover_by_day": turnover_by_day,
products_sold_pie, "average_basket": Basket.objects.average_basket(),
products_turnover_pie, "average_basket_by_day": average_basket_by_day,
) = self.get_products_plots(products) "products": products,
by_hour_plot = self.by_hour_plot(baskets) "products_plot": products_plot,
context.update( "products_sold_pie": products_sold_pie,
{ "products_turnover_pie": products_turnover_pie,
"turnover": Basket.objects.turnover(), "by_hour_plot": by_hour_plot(baskets),
"turnover_by_day": turnover_by_day, "payment_methods": PaymentMethod.objects.with_turnover().with_sold(),
"average_basket": Basket.objects.average_basket(), "no_payment_method": Basket.objects.no_payment_method().priced(),
"average_basket_by_day": average_basket_by_day, }
"products": products, return TemplateResponse(request, template_name, context)
"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
def get_products_plots(self, products: ProductQuerySet):
labels, sold, turnover = self.get_products_data_for_plot(products)
x = np.arange(len(labels)) def get_products_plots(products: ProductQuerySet):
width = 0.4 labels, sold, turnover = get_products_data_for_plot(products)
fig: Figure = plt.figure()
fig.suptitle(_("Sales by product"))
color = "tab:orange" x = np.arange(len(labels))
ax1: Axes = fig.add_subplot() width = 0.4
bar: BarContainer = ax1.bar(x - width / 2, sold, width=width, color=color) fig: Figure = plt.figure()
ax1.bar_label(bar) fig.suptitle(_("Sales by product"))
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)
color = "tab:blue" color = "tab:orange"
ax2: Axes = ax1.twinx() ax1: Axes = fig.add_subplot()
bar = ax2.bar(x + width / 2, turnover, width=width, color=color) bar: BarContainer = ax1.bar(x - width / 2, sold, width=width, color=color)
ax2.bar_label(bar, fmt="%d") ax1.bar_label(bar)
ax2.set_ylabel(_("Turnover by product"), color=color) ax1.tick_params(axis="x", rotation=15)
ax2.yaxis.set_major_formatter(ticker.FormatStrFormatter("%.2f")) ax1.tick_params(axis="y", labelcolor=color)
ax2.tick_params(axis="y", labelcolor=color) ax1.set_xticks(x, labels)
ax1.set_ylabel(_("# sold"), color=color)
fig.tight_layout() color = "tab:blue"
img1 = self.get_image_from_fig(fig) 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.tight_layout()
fig.suptitle(_("# sold")) img1 = get_image_from_fig(fig)
ax1 = fig.add_subplot()
ax1.pie(sold, labels=labels, autopct="%d%%")
fig.tight_layout()
img2 = self.get_image_from_fig(fig)
fig = plt.figure() fig = plt.figure()
fig.suptitle(_("Turnover by product")) fig.suptitle(_("# sold"))
ax1 = fig.add_subplot() ax1 = fig.add_subplot()
ax1.pie(turnover, labels=labels, autopct="%d%%") ax1.pie(sold, labels=labels, autopct="%d%%")
fig.tight_layout() fig.tight_layout()
img3 = self.get_image_from_fig(fig) 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): return img1, img2, img3
labels = []
sold = []
turnover = []
for product in products:
labels.append(product.name)
sold.append(product.sold)
turnover.append(product.turnover / 100)
return labels, sold, turnover
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" def get_products_data_for_plot(products):
ax1.bar(labels, counts, width=1 / hours_in_day, color=color) labels = []
ax1.tick_params(axis="x", rotation=15) sold = []
ax1.xaxis.set_minor_locator(HourLocator()) turnover = []
tz = ZoneInfo(settings.TIME_ZONE) for product in products:
locator = AutoDateLocator(tz=tz) labels.append(product.name)
ax1.xaxis.set_major_locator(locator) sold.append(product.sold)
ax1.xaxis.set_major_formatter(ConciseDateFormatter(locator, tz=tz)) turnover.append(product.turnover / 100)
ax1.set_ylabel(_("Basket count by hour"), color=color) return labels, sold, turnover
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() def by_hour_plot(baskets):
return self.get_image_from_fig(fig) 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): color = "tab:orange"
current: datetime.datetime = baskets[0].created_at ax1.bar(labels, counts, width=1 / hours_in_day, color=color)
current = current.replace(minute=0, second=0, microsecond=0) ax1.tick_params(axis="x", rotation=15)
end: datetime.datetime = baskets[-1].created_at ax1.xaxis.set_minor_locator(HourLocator())
basket_index = 0 tz = ZoneInfo(settings.TIME_ZONE)
labels = [] locator = AutoDateLocator(tz=tz)
counts = [] ax1.xaxis.set_major_locator(locator)
turnovers = [] ax1.xaxis.set_major_formatter(ConciseDateFormatter(locator, tz=tz))
while current < end: ax1.set_ylabel(_("Basket count by hour"), color=color)
end_slot = current + datetime.timedelta(hours=1) 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] basket = baskets[basket_index]
count = 0 labels.append(current.astimezone(ZoneInfo(settings.TIME_ZONE)))
turnover = 0 counts.append(count)
while basket.created_at < end_slot: turnovers.append(turnover)
count += 1 current = end_slot
turnover += basket.price / 100 return labels, counts, turnovers
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
def get_image_from_fig(self, fig):
image_data = StringIO() def get_image_from_fig(fig):
fig.savefig(image_data, format="svg") image_data = StringIO()
image_data.seek(0) fig.savefig(image_data, format="svg")
img1 = image_data.getvalue() image_data.seek(0)
return img1 img1 = image_data.getvalue()
return img1